Реализовать или расширить?
Допустим, у меня есть интерфейс C и класс A, который его реализует.
Теперь предположим, что я хочу изменить реализацию A для всех функций в C, кроме одной, с классом B. Должен ли я расширять A и переопределять C или мне следует реализовывать C как новый независимый класс?
Допустим, некоторые компании телефонов хотят реализовать свой базовый интерфейс Phone, и у них уже есть какая-то старая реализация OldPhone, в которой они хотят сохранить все свои функциональные возможности: позвонить, повесить трубку, отправить SMS- и они не хотят изменить все это, но они хотят, чтобы другие новые функциональные возможности сделали их новый смартфон - должны ли они расширить OldPhone или создать новый класс?
Я хотел бы знать, когда я должен расширять классы и когда я должен реализовывать интерфейсы с самого начала - с одной стороны, я не хочу копировать код, но с другой стороны, иногда типы, даже если они оба являются A, концептуально разные.
5 ответов
Если у вас есть код, который вы хотите использовать повторно, то расширение является лучшим выбором.
Но спросите себя: новый телефон - лучшая версия старого или совершенно новый с похожими функциями?
- В первом случае: расширение.
- Вторая абстракция - это то, что вы ищете: извлеките общие функции и поместите их в абстрактный класс, который будет расширяться обоими классами.
Если в OldPhone
а также SmartPhone
Вы могли бы извлечь это в (абстрактный) класс и позволить и расширять это, и позволять им иметь другой как различия.
В противном случае перейти на интерфейс
Теперь смотрите ваши требования говорит, что B должен вести себя точно так же, как A для большей части функциональности. Теперь предположим, что вы решили реализовать то есть B implments C. Затем, если в будущем логика класса A изменится, вам также придется изменить класс B. Так что это не подходит для ваших требований.
Теперь предположим, что вы расширяете A. Но тогда вы тесно связаны с A. Как будто A изменяет, ваш Большой также будет вести себя так же.
Поэтому постарайтесь получить лучшее из двух слов, используя композицию. B реализует C и использует A в качестве делегата для реализации этих методов. Мой ответ для вашего случая не обязательно будет лучшим выбором в каждом случае.
Это скорее зависит от того, сколько изменится с C (старый телефон) на B (новый телефон). Использование телефонов в качестве примера довольно хорошо, потому что старый телефон концептуально делает то же самое, что и новый телефон - он делает телефонные звонки.
Трудность наступает, когда реализация C тесно связана с работой телефона. Скажем, например, что старый телефон настаивает на использовании кругового звонка, который вы поворачиваете для выбора набираемого номера, а не клавиатуры. В этой ситуации, если у вас есть полный контроль над кодом, вы можете попытаться создать слой абстракции между интерфейсом и старым телефоном. Уровень абстракции отражает только то, что значит быть телефоном, и ничего более.
Получив слой абстракции, вы можете выбрать, следует ли расширить старый телефон, чтобы создать новый телефон, или создать новую реализацию. В любом случае это должно быть проще, чем сейчас, так как старый телефон будет содержать гораздо меньше кода и только те биты, которые специфичны для него.
Практическое правило: вы должны расширять класс только в том случае, если есть родительско-дочерние отношения. В противном случае перейдите к реализации интерфейса.