В попытке полностью понять, как решить проблемы множественного наследования в Java, у меня есть классический вопрос, который мне нужно прояснить.
Допустим , у меня есть класс Animal
это имеет вложенные классы Bird
и Horse
и мне нужно сделать класс , Pegasus
который простирается от Bird
и Horse
так Pegasus
является как птица и лошадь.
Я думаю, что это классическая проблема с бриллиантами. Из того, что я могу понять , классический способ решить это сделать Animal
, Bird
и Horse
классы интерфейсов и осуществлять Pegasus
из них.
Мне было интересно, есть ли другой способ решить проблему, в которой я все еще могу создавать объекты для птиц и лошадей. Если бы существовал способ создавать животных, это было бы замечательно, но не обязательно.
public class Pegasus extends Horse implements Flying
.Ответы:
Вы можете создать интерфейсы для классов животных (класс в биологическом смысле), например
public interface Equidae
для лошадей иpublic interface Avialae
птиц (я не биолог, поэтому термины могут быть неправильными).Тогда вы все еще можете создать
и
а также
Добавление из комментариев:
Чтобы уменьшить количество дублирующегося кода, вы можете создать абстрактный класс, содержащий большую часть общего кода животных, которые вы хотите реализовать.
Обновить
Я хотел бы добавить еще одну деталь. Как отмечает Брайан , это уже известно ОП.
Тем не менее, я хочу подчеркнуть, что я предлагаю обойти проблему «мульти-наследования» с интерфейсами и что я не рекомендую использовать интерфейсы, которые представляют уже конкретный тип (например, Bird), а больше поведения (другие ссылаются на типирование утки, что тоже хорошо, но я имею в виду просто: биологический класс птиц, Avialae). Я также не рекомендую использовать имена интерфейсов, начинающиеся с заглавной буквы «я», например
IBird
, которая ничего не говорит о том, зачем вам нужен интерфейс. В этом отличие вопроса: создайте иерархию наследования с использованием интерфейсов, используйте абстрактные классы, когда это полезно, реализуйте конкретные классы, где это необходимо, и используйте делегирование, если это необходимо.источник
AbstractHorse
, который также может быть использован для создания зебр или других животных, похожих на лошадей.AbstractEquidae
, будет более подходящим, чемAbstractHorse
. Было бы странно, чтобы зебра расширяла абстрактную лошадь. Хороший ответ, кстати.Duck
реализацию классовAvialae
?Существует два основных подхода к объединению объектов:
Это работает так, что у вас есть объект Animal. Внутри этого объекта вы затем добавляете дополнительные объекты, которые задают необходимые вам свойства и поведение.
Например:
Теперь
IFlier
просто выглядит так:Так
Bird
выглядит вот так:Теперь у вас есть все преимущества наследования. Вы можете повторно использовать код. Вы можете иметь коллекцию IFliers и использовать все остальные преимущества полиморфизма и т. Д.
Однако у вас также есть все гибкость от композиции. Вы можете применять столько разных интерфейсов и составных классов поддержки, сколько вам нужно, к каждому типу
Animal
- с таким количеством контроля, который вам необходим для настройки каждого бита.Стратегия Pattern альтернативный подход к композиции
Альтернативный подход в зависимости от того, что и как вы делаете, заключается в том, чтобы
Animal
базовый класс содержал внутреннюю коллекцию для хранения списка различных вариантов поведения. В этом случае вы используете что-то ближе к шаблону стратегии. Это дает преимущества с точки зрения упрощения кода (напримерHorse
, не нужно ничего знать оQuadruped
илиHerbivore
), но если вы также не будете использовать интерфейсный подход, вы потеряете много преимуществ полиморфизма и т. Д.источник
getFlier()
должно быть переопределено для каждого вида птиц.MathsTeacher
иEnglishTeacher
наследоватьTeacher
,ChemicalEngineer
иMaterialsEngineer
т.д. наследоватьEngineer
.Teacher
иEngineer
оба реализуютComponent
.Person
Тогда просто есть списокComponent
с, и вы можете дать им правильныеComponent
S для этогоPerson
. то естьperson.getComponent(Teacher.class)
,person.getComponent(MathsTeacher.class)
и т.д.У меня глупая идея
источник
Могу ли я предложить концепцию Duck-typing ?
Скорее всего, вы бы хотели, чтобы Pegasus расширял интерфейс Bird и Horse, но типирование утки фактически предполагает, что вы должны скорее наследовать поведение . Как уже говорилось в комментариях, пегас не птица, но может летать. Так что ваш Пегас должен наследовать
Flyable
-интерфейс и, скажем,Gallopable
-интерфейс.Этот вид концепции используется в шаблоне стратегии . Приведенный пример показывает , на самом деле, как утка наследует
FlyBehaviour
иQuackBehaviour
и по- прежнему могут быть утки, напримерRubberDuck
, которые не могут летать. Они могли бы также сделатьDuck
расширениеBird
-классом, но тогда они отказались бы от некоторой гибкости, потому что каждыйDuck
мог бы летать, даже бедныеRubberDuck
.источник
С технической точки зрения, вы можете расширять только один класс за раз и реализовывать несколько интерфейсов, но при разработке программного обеспечения я предпочел бы предложить решение для конкретной проблемы, которое обычно не отвечает. Кстати, это хорошая ОО-практика - не расширять конкретные классы / расширять только абстрактные классы для предотвращения нежелательного наследования - нет такого понятия, как «животное» и использование объекта животного, а только конкретных животных.
источник
В Java 8, которая все еще находится на стадии разработки по состоянию на февраль 2014 года, вы можете использовать методы по умолчанию для достижения своего рода C ++ - как множественное наследование. Вы также можете взглянуть на этот учебник, который показывает несколько примеров, с которыми легче начать работать, чем с официальной документацией.
источник
Держать лошадь в конюшне с половиной двери безопасно, так как лошадь не может пройти через половину двери. Поэтому я настраиваю службу содержания лошадей, которая принимает любой предмет типа лошади и помещает его в конюшню с половиной двери.
Так похожа ли лошадь на животное, способное управлять даже лошадью?
Раньше я много думал о множественном наследовании, однако теперь, когда я программирую более 15 лет, я больше не беспокоюсь о реализации множественного наследования.
Чаще всего, когда я пытался справиться с дизайном, который указывал на множественное наследование, я позже приходил к выводу, что я упустил понимание проблемной области.
ИЛИ
источник
У Java нет проблемы множественного наследования, поскольку у нее нет множественного наследования. Это сделано для того, чтобы решить реальную проблему множественного наследования (проблема алмаза).
Существуют разные стратегии смягчения проблемы. Наиболее быстро достижимым является объект Composite, который предлагает Павел (по сути, как C ++ справляется с этим). Я не знаю, есть ли множественное наследование через линеаризацию C3 (или подобное) на картах для будущего Java, но я сомневаюсь в этом.
Если ваш вопрос носит академический характер, то правильное решение состоит в том, что Птица и Лошадь более конкретны, и неверно полагать, что Пегас - это просто Птица и Лошадь вместе взятые. Правильнее будет сказать, что у Пегаса есть определенные внутренние свойства, общие с Птицами и Лошадями (то есть у них могут быть общие предки). Это может быть достаточно смоделировано, как указывает ответ Морица.
источник
Я думаю, что это очень сильно зависит от ваших потребностей и того, как ваши классы животных будут использоваться в вашем коде.
Если вы хотите использовать методы и функции ваших реализаций Horse и Bird внутри вашего класса Pegasus, то вы можете реализовать Pegasus как композицию Bird и Horse:
Другая возможность - использовать Entity-Component-System. подход вместо наследования для определения ваших животных. Конечно, это означает, что у вас не будет отдельных классов Java животных, но вместо этого они определяются только их компонентами.
Некоторый псевдокод для подхода Entity-Component-System может выглядеть следующим образом:
источник
вы можете иметь иерархию интерфейсов, а затем расширять ваши классы из выбранных интерфейсов:
и затем определите ваши классы по мере необходимости, расширяя определенный интерфейс:
источник
IBird
иIHorse
следует реализоватьIAnimal
вместоAnimal
Э-э, ваш класс может быть подклассом только для одного другого, но, тем не менее, вы можете реализовать столько интерфейсов, сколько пожелаете.
Пегас на самом деле является лошадью (это особый случай лошади), которая способна летать (что является «навыком» этой особой лошади). С другой стороны, вы можете сказать, что Пегас - это птица, которая может ходить, и она легкая - все зависит от того, как вам будет легче писать код.
Как и в вашем случае вы можете сказать:
источник
Интерфейсы не имитируют множественное наследование. Создатели Java считали множественное наследование неправильным, поэтому в Java такого нет.
Если вы хотите объединить функциональность двух классов в один - используйте композицию объектов. Т.е.
А если вы хотите предоставить определенные методы, определите их и позвольте им делегировать вызов соответствующему контроллеру.
Здесь интерфейсы могут пригодиться - если
Component1
реализует интерфейсInterface1
иComponent2
реализуетInterface2
, вы можете определитьТак что вы можете использовать объекты взаимозаменяемо, где это позволяет контекст.
Так что, на мой взгляд, вы не можете попасть в проблему алмазов.
источник
Как вы уже знаете, множественное наследование классов в Java невозможно, но возможно с интерфейсами. Вы также можете рассмотреть возможность использования шаблона проектирования композиции.
Я написал очень полную статью о композиции несколько лет назад ...
/codereview/14542/multiple-inheritance-and-composition-with-java-and-c-updated
источник
Посмотрите на пример ниже для лучшего понимания
Когда использовать шаблон декоратора?
источник
Чтобы уменьшить сложность и упростить язык, множественное наследование не поддерживается в Java.
Рассмотрим сценарий, в котором A, B и C - три класса. Класс C наследует классы A и B. Если классы A и B имеют один и тот же метод, и вы вызываете его из дочернего объекта класса, возникнет неоднозначность при вызове метода класса A или B.
Поскольку ошибки времени компиляции лучше, чем ошибки времени выполнения, java отображает ошибку времени компиляции, если вы унаследовали 2 класса. Так что, будь у вас тот же метод или другой, теперь будет ошибка времени компиляции.
источник
Для решения проблемы множественного наследования в Java → используется интерфейс
J2EE (ядро JAVA) Примечания г-на KVR Стр. 51
источник