Множественное наследование 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
.