В попытке полностью понять, как решить проблемы множественного наследования Java, у меня возник классический вопрос, который мне нужно прояснить.
Допустим, у меня есть класс, Animal у которого есть подклассы Bird и Horse и мне нужно создать класс, Pegasus который расширяется от Bird и Horse поскольку Pegasus это и птица, и лошадь.
Я думаю, что это классическая проблема с бриллиантами. Насколько я могу понять, классический способ решить эту проблему - создать интерфейсы классов Animal, Bird и Horse и реализовать Pegasus на их основе.
Мне было интересно, есть ли другой способ решить проблему, при котором я все еще могу создавать объекты для птиц и лошадей. Если бы был способ также создавать животных, это было бы здорово, но не обязательно.
Переведено автоматически
Ответ 1
Вы могли бы создавать интерфейсы для классов животных (class в биологическом смысле), таких как public interface Equidae для лошадей и public interface Avialae для птиц (я не биолог, поэтому термины могут быть неправильными).
Тогда вы все еще можете создать
publicclassBirdimplementsAvialae { }
и
publicclassHorseimplementsEquidae {}
а также
publicclassPegasusimplementsAvialae, Equidae {}
Добавление из комментариев:
Чтобы уменьшить количество дублирующегося кода, вы могли бы создать абстрактный класс, содержащий большую часть общего кода animals, который вы хотите реализовать.
Я хотел бы добавить еще одну деталь. Как замечает Брайан, это то, что OP уже знала.
Однако я хочу подчеркнуть, что я предлагаю обойти проблему "множественного наследования" с интерфейсами и что я не рекомендую использовать интерфейсы, которые уже представляют конкретный тип (например, Bird), а скорее поведение (другие ссылаются на утиный тип, что тоже хорошо, но я имею в виду просто: биологический класс птиц, Avialae). Я также не рекомендую использовать имена интерфейсов, начинающиеся с заглавной буквы "I", такие как IBird, которые просто ничего не говорят о том, зачем вам нужен интерфейс. В этом разница в вопросе: постройте иерархию наследования с использованием интерфейсов, используйте абстрактные классы, когда это полезно, реализуйте конкретные классы, где это необходимо, и используйте делегирование, если это уместно.
Ответ 2
Существует два фундаментальных подхода к объединению объектов вместе:
Первое - это наследование. Как вы уже определили, ограничения наследования означают, что вы не можете делать здесь то, что вам нужно.
Второй - это композиция. Поскольку наследование не удалось, вам нужно использовать композицию.
Это работает так, что у вас есть объект Animal . Затем внутри этого объекта вы добавляете дополнительные объекты, которые предоставляют требуемые вам свойства и поведение.
Например:
Bird расширяет возможности Animal, если это возможно
Теперь у вас есть все преимущества наследования. Вы можете повторно использовать код. У вас может быть коллекция IFliers и вы можете использовать все другие преимущества полиморфизма и т.д.
Однако у вас также есть вся гибкость, обеспечиваемая композицией. Вы можете применить столько различных интерфейсов и составного вспомогательного класса, сколько захотите, к каждому типу Animal - с таким количеством контроля, сколько вам нужно, над настройкой каждого бита.
Альтернативный подход к составлению шаблона стратегии
Альтернативный подход, зависящий от того, что и как вы делаете, заключается в том, чтобы Animal базовый класс содержал внутреннюю коллекцию для хранения списка различных вариантов поведения. В этом случае вы в конечном итоге используете что-то более близкое к шаблону стратегии. Это дает преимущества с точки зрения упрощения кода (например, Horse не нужно ничего знать о Quadruped или Herbivore), но если вы также не применяете интерфейсный подход, вы теряете многие преимущества полиморфизма и т.д.
Скорее всего, вы хотели бы, чтобы Pegasus расширил интерфейс Bird and a Horse, но типизация duck на самом деле предполагает, что вам следует скорее унаследовать поведение. Как уже говорилось в комментариях, пегас не птица, но он может летать. Таким образом, ваш Pegasus должен скорее унаследовать Flyable-интерфейс и, скажем, Gallopable-interface .
Такого рода концепция используется в шаблоне стратегии. Приведенный пример фактически показывает вам, как утка наследует FlyBehaviour и QuackBehaviour и все же могут быть утки, например RubberDuck, которые не умеют летать. Они могли бы также создать Duck extend a Bird-класс, но тогда они отказались бы от некоторой гибкости, потому что каждый Duck мог бы летать, даже бедный RubberDuck.