Джейм Гослинг сказал
«Вы должны по возможности избегать наследования реализации».
и вместо этого используйте наследование интерфейса.
Но почему? Как мы можем избежать наследования структуры объекта, используя ключевое слово «extends», и в то же время сделать наш код «Object Oriented»?
Может ли кто-нибудь привести пример «Объектно-ориентированный», иллюстрирующий эту концепцию в сценарии типа «заказ книги в книжном магазине»?
Ответы:
Гослинг не сказал, чтобы избежать использования ключевого слова extends ... он просто сказал, что не использует наследование в качестве средства для достижения повторного использования кода.
Наследование само по себе неплохо и является очень мощным (необходимым) инструментом для создания ОО-структур. Однако, когда он не используется должным образом (то есть, когда используется для чего - то другого, кроме создания структур Object), он создает код, который очень тесно связан и очень сложен в обслуживании.
Поскольку наследование должно использоваться для создания полиморфных структур, это можно сделать так же легко с помощью интерфейсов, отсюда и утверждение использовать интерфейсы, а не классы при проектировании ваших объектных структур. Если вы обнаружите, что используете extends в качестве средства, позволяющего избежать копирования кода из одного объекта в другой, возможно, вы используете его неправильно, и вам лучше использовать специализированный класс для обработки этой функциональности и совместного использования этого кода посредством композиции.
Прочитайте о принципе подстановки Лискова и поймите его. Всякий раз, когда вы собираетесь расширить класс (или интерфейс в этом отношении), спрашивайте себя, нарушаете ли вы принцип, если да, то вы, скорее всего, (а) используете наследование неестественным образом. Это легко сделать и часто кажется правильным, но это сделает ваш код более сложным в поддержке, тестировании и развитии.
--- Редактировать Этот ответ является довольно хорошим аргументом, чтобы ответить на вопрос в более общих терминах.
источник
На заре объектно-ориентированного программирования наследование реализации было золотым молотком повторного использования кода. Если в каком-то классе была какая-то часть функциональности, которая вам была нужна, нужно было публично наследовать от этого класса (это были времена, когда C ++ был более распространенным, чем Java), и вы получали эту функциональность «бесплатно».
Кроме того, это не было действительно бесплатно. Результатом стали чрезвычайно запутанные иерархии наследования, которые было почти невозможно понять, включая деревья наследования в форме ромба, с которыми было трудно обращаться. Это одна из причин, по которой разработчики Java решили не поддерживать множественное наследование классов.
Совет Гослинга (и другие утверждения), как будто это было сильное лекарство от довольно серьезного заболевания, и вполне возможно, что некоторые дети были выброшены с водой из ванны. Нет ничего плохого в использовании наследования для моделирования отношений между классами, которые действительно есть
is-a
. Но если вы просто хотите использовать функциональность из другого класса, вам лучше сначала изучить другие варианты.источник
Наследство приобрело довольно страшную репутацию в последнее время. Одна из причин избегать этого связана с принципом «составление по наследованию», который в основном побуждает людей не использовать наследование там, где это не истинные отношения, просто для того, чтобы сохранить некоторую запись кода. Такое наследование может привести к тому, что некоторые проблемы будут трудно найти и исправить. Причина, по которой ваш друг, вероятно, предложил использовать интерфейс, заключается в том, что интерфейсы предоставляют более гибкое и удобное решение для распределенных API-интерфейсов. Они чаще позволяют вносить изменения в API, не нарушая пользовательский код, тогда как использование классов часто нарушает пользовательский код. Лучшим примером этого в .Net является разница в использовании между List и IList.
источник
Потому что вы можете расширить только один класс, в то время как вы можете реализовать много интерфейсов.
Однажды я слышал, что наследование определяет, кто вы есть, но интерфейсы определяют роль, которую вы можете играть.
Интерфейсы также позволяют лучше использовать полиморфизм.
Это может быть частью причины.
источник
Меня тоже этому учили, и я предпочитаю интерфейсы, где это возможно (конечно, я все еще использую наследование там, где это имеет смысл).
Одна вещь, я думаю, что это делает, это отделить ваш код от конкретных реализаций. Скажем, у меня есть класс ConsoleWriter, который передается в метод для записи чего-либо и записывает это в консоль. Теперь допустим, что я хочу переключиться на печать в окне графического интерфейса. Что ж, теперь я должен либо изменить метод, либо написать новый, который принимает GUIWriter в качестве параметра. Если бы я начал с определения интерфейса IWriter, а метод взял IWriter, я мог бы начать с ConsoleWriter (который будет реализовывать интерфейс IWriter), а затем позже написать новый класс с именем GUIWriter (который также реализует интерфейс IWriter), а затем я нужно было бы просто выключить передаваемый класс.
Еще одна вещь (которая верна для C #, но не для Java) - это то, что вы можете расширить только один класс, но реализовать много интерфейсов. Допустим, у меня были классы под названием Учитель, Учитель математики и Учитель истории. Теперь MathTeacher и HistoryTeacher простираются от Teacher, но что если нам нужен класс, представляющий кого-то, кто является одновременно MathTeacher и HistoryTeacher. Это может стать довольно грязным, когда вы пытаетесь наследовать от нескольких классов, когда вы можете делать это только по одному (есть способы, но они не совсем оптимальны). С помощью интерфейсов вы можете иметь 2 интерфейса, называемых IMathTeacher и IHistoryTeacher, а затем иметь один класс, который расширяется от Teacher и реализует эти 2 интерфейса.
Недостатком использования интерфейсов является то, что иногда я вижу, как люди дублируют код (поскольку вы должны создать реализацию для каждого класса, реализующего интерфейс), однако существует чистое решение этой проблемы, например, использование таких вещей, как делегаты (не уверен что такое эквивалент Java).
Думаю, что основной причиной использования интерфейсов вместо наследования является разделение кода реализации, но не думайте, что наследование является злом, поскольку оно все еще очень полезно.
источник
Если вы наследуете от класса, вы берете зависимость не только от этого класса, но вы также должны соблюдать любые неявные контракты, созданные клиентами этого класса. Дядя Боб дает отличный пример того, как легко нарушить принцип подстановки Лискова, используя базовый квадрат, расширяющий дилемму прямоугольника . Интерфейсы, поскольку они не имеют реализации, также не имеют скрытых контрактов.
источник