Вопрос-ответ

Java Multiple Inheritance

Множественное наследование Java

В попытке полностью понять, как решить проблемы множественного наследования Java, у меня возник классический вопрос, который мне нужно прояснить.

Допустим, у меня есть класс, Animal у которого есть подклассы Bird и Horse и мне нужно создать класс, Pegasus который расширяется от Bird и Horse поскольку Pegasus это и птица, и лошадь.

Я думаю, что это классическая проблема с бриллиантами. Насколько я могу понять, классический способ решить эту проблему - создать интерфейсы классов Animal, Bird и Horse и реализовать Pegasus на их основе.

Мне было интересно, есть ли другой способ решить проблему, при котором я все еще могу создавать объекты для птиц и лошадей. Если бы был способ также создавать животных, это было бы здорово, но не обязательно.

Переведено автоматически
Ответ 1

Вы могли бы создавать интерфейсы для классов животных (class в биологическом смысле), таких как public interface Equidae для лошадей и public interface Avialae для птиц (я не биолог, поэтому термины могут быть неправильными).

Тогда вы все еще можете создать

public class Bird implements Avialae {
}

и

public class Horse implements Equidae {}

а также

public class Pegasus implements Avialae, Equidae {}

Добавление из комментариев:

Чтобы уменьшить количество дублирующегося кода, вы могли бы создать абстрактный класс, содержащий большую часть общего кода animals, который вы хотите реализовать.

public abstract class AbstractHorse implements Equidae {}

public class Horse extends AbstractHorse {}

public class Pegasus extends AbstractHorse implements Avialae {}

Обновить

Я хотел бы добавить еще одну деталь. Как замечает Брайан, это то, что OP уже знала.

Однако я хочу подчеркнуть, что я предлагаю обойти проблему "множественного наследования" с интерфейсами и что я не рекомендую использовать интерфейсы, которые уже представляют конкретный тип (например, Bird), а скорее поведение (другие ссылаются на утиный тип, что тоже хорошо, но я имею в виду просто: биологический класс птиц, Avialae). Я также не рекомендую использовать имена интерфейсов, начинающиеся с заглавной буквы "I", такие как IBird, которые просто ничего не говорят о том, зачем вам нужен интерфейс. В этом разница в вопросе: постройте иерархию наследования с использованием интерфейсов, используйте абстрактные классы, когда это полезно, реализуйте конкретные классы, где это необходимо, и используйте делегирование, если это уместно.

Ответ 2

Существует два фундаментальных подхода к объединению объектов вместе:


  • Первое - это наследование. Как вы уже определили, ограничения наследования означают, что вы не можете делать здесь то, что вам нужно.

  • Второй - это композиция. Поскольку наследование не удалось, вам нужно использовать композицию.

Это работает так, что у вас есть объект Animal . Затем внутри этого объекта вы добавляете дополнительные объекты, которые предоставляют требуемые вам свойства и поведение.

Например:


  • Bird расширяет возможности Animal, если это возможно

  • Horse расширяет Animal, реализует IHerbivore, IQuadruped

  • Pegasus расширяет возможности Animal, реализует IHerbivore, IQuadruped, IFlier

Теперь IFlier выглядит примерно так:

 interface IFlier {
Flier getFlier();
}

Итак, Bird выглядит примерно так:

 class Bird extends Animal implements IFlier {
Flier flier = new Flier();
public Flier getFlier() { return flier; }
}

Теперь у вас есть все преимущества наследования. Вы можете повторно использовать код. У вас может быть коллекция IFliers и вы можете использовать все другие преимущества полиморфизма и т.д.

Однако у вас также есть вся гибкость, обеспечиваемая композицией. Вы можете применить столько различных интерфейсов и составного вспомогательного класса, сколько захотите, к каждому типу Animal - с таким количеством контроля, сколько вам нужно, над настройкой каждого бита.

Альтернативный подход к составлению шаблона стратегии

Альтернативный подход, зависящий от того, что и как вы делаете, заключается в том, чтобы Animal базовый класс содержал внутреннюю коллекцию для хранения списка различных вариантов поведения. В этом случае вы в конечном итоге используете что-то более близкое к шаблону стратегии. Это дает преимущества с точки зрения упрощения кода (например, Horse не нужно ничего знать о Quadruped или Herbivore), но если вы также не применяете интерфейсный подход, вы теряете многие преимущества полиморфизма и т.д.

Ответ 3

У меня есть глупая идея:

public class Pegasus {
private Horse horseFeatures;
private Bird birdFeatures;

public Pegasus(Horse horse, Bird bird) {
this.horseFeatures = horse;
this.birdFeatures = bird;
}

public void jump() {
horseFeatures.jump();
}

public void fly() {
birdFeatures.fly();
}
}
Ответ 4

Могу ли я предложить концепцию Duck-typing?

Скорее всего, вы хотели бы, чтобы Pegasus расширил интерфейс Bird and a Horse, но типизация duck на самом деле предполагает, что вам следует скорее унаследовать поведение. Как уже говорилось в комментариях, пегас не птица, но он может летать. Таким образом, ваш Pegasus должен скорее унаследовать Flyable-интерфейс и, скажем, Gallopable-interface .

Такого рода концепция используется в шаблоне стратегии. Приведенный пример фактически показывает вам, как утка наследует FlyBehaviour и QuackBehaviour и все же могут быть утки, например RubberDuck, которые не умеют летать. Они могли бы также создать Duck extend a Bird-класс, но тогда они отказались бы от некоторой гибкости, потому что каждый Duck мог бы летать, даже бедный RubberDuck.

java oop