Многократное наследование Java
В попытке полностью понять, как решить проблемы множественного наследования Java, у меня есть классический вопрос, который мне нужно уточнить.
Предположим, что у меня есть класс Animal
, у него есть подклассы Bird
и Horse
, и мне нужно создать класс Pegasus
, который простирается от Bird
и Horse
, так как Pegasus
является и птицей и лошадь.
Я думаю, что это классическая проблема с алмазами. Из того, что я могу понять, классический способ решения этого - сделать интерфейсы классов Animal
, Bird
и Horse
и реализовать из них Pegasus
.
Мне было интересно, есть ли другой способ решить проблему, в которой я могу создавать объекты для птиц и лошадей. Если бы существовал способ создать животных, это было бы здорово, но не обязательно.
Ответы
Ответ 1
Вы можете создавать интерфейсы для классов животных (класс в биологическом смысле), например public interface Equidae
для лошадей и public interface Avialae
для птиц (я не биолог, поэтому эти термины могут быть неправильными).
Затем вы можете создать
public class Bird implements Avialae {
}
и
public class Horse implements Equidae {}
а также
public class Pegasus implements Avialae, Equidae {}
Добавление комментариев:
Чтобы уменьшить дублирующийся код, вы можете создать абстрактный класс, который содержит большую часть общего кода животных, которые вы хотите реализовать.
public abstract class AbstractHorse implements Equidae {}
public class Horse extends AbstractHorse {}
public class Pegasus extends AbstractHorse implements Avialae {}
Update
Я хотел бы добавить еще одну деталь. Как Брайан замечает, это то, что ОП уже знал.
Однако я хочу подчеркнуть, что я предлагаю обойти проблему "множественного наследования" с интерфейсами и что я не рекомендую использовать интерфейсы, которые представляют уже конкретный тип (например, Bird), но больше поведение ( другие относятся к утиному набору, что тоже хорошо, но я имею в виду: биологический класс птиц, Avialae). Я также не рекомендую использовать имена интерфейсов, начинающиеся с капитала "I", например IBird
, который просто ничего не говорит о том, зачем нужен интерфейс. Это отличие от вопроса: построить иерархию наследования с использованием интерфейсов, использовать абстрактные классы, когда это полезно, реализовать конкретные классы там, где это необходимо, и использовать делегирование, если это необходимо.
Ответ 2
Существует два основных подхода к объединению объектов:
- Первый Наследование. Как вы уже определили, ограничения наследования означают, что вы не можете делать то, что вам нужно здесь.
- Вторая Состав. Поскольку наследование не удалось, вам нужно использовать композицию.
Как это работает, у вас есть объект Animal. Внутри этого объекта вы добавляете дополнительные объекты, которые предоставляют требуемые свойства и поведения.
Например:
- Птица расширяет Animal реализует IFlier
- Лошадь расширяет Животное реализует 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 Horse, но утка набирает на самом деле предположение, что вы должны скорее наследовать поведение. Как уже отмечалось в комментариях, пегас - это не птица, но он может летать. Таким образом, ваш Pegasus должен скорее наследовать интерфейс Flyable
и позволяет сказать Gallopable
-interface.
Этот вид концепции используется в Стратегическом шаблоне. Данный пример фактически показывает вам, как утка наследует FlyBehaviour
и QuackBehaviour
, и все еще могут быть утки, например. RubberDuck
, который не может летать. Они могли бы также сделать Duck
расширить класс Bird
, но тогда они отказались бы от некоторой гибкости, потому что каждый Duck
мог бы летать, даже бедный RubberDuck
.
Ответ 5
С технической точки зрения, вы можете продлить только один класс за раз и реализовать несколько интерфейсов, но когда вы занимаетесь разработкой программного обеспечения, я предпочел бы предложить решение проблемы, которое обычно не подлежит обсуждению. Кстати, это хорошая практика OO, а не расширение конкретных классов/расширение только абстрактных классов для предотвращения нежелательного поведения наследования - нет такого понятия, как "животное" и не используется объект животного, а только конкретные животные.
Ответ 6
Безопасно держать лошадь в конюшне с половиной двери, так как лошадь не может преодолеть половину двери. Поэтому я настраиваю службу конного жилья, которая принимает любой предмет лошади типа и помещает его в конюшню с половиной двери.
Как лошадь, как животное, которое может летать даже на лошади?
Я много думал о множественном наследовании, однако теперь, когда я программировал более 15 лет, я больше не забочусь о реализации множественного наследования.
Чаще всего, когда я пытался справиться с дизайном, который указывал на множественное наследование, я позже пришел к выводу, что я пропустил понимание проблемного домена.
ИЛИ
Если это выглядит как утка и шарлатанцы, как утка, но это нужно батареи, у вас, вероятно, есть неправильная абстракция.
Ответ 7
В Java 8, который по-прежнему находится на стадии разработки с февраля 2014 года, вы можете использовать методы по умолчанию для достижения своего рода С++ похожее на множественное наследование.
Вы также можете посмотреть этот учебник, который показывает несколько примеров, с которыми должно быть проще начать работать, чем официальная документация.
Ответ 8
Java не имеет проблемы с множественным наследованием, поскольку она не имеет множественного наследования. Это по дизайну, чтобы решить реальную проблему множественного наследования (проблема с алмазом).
Существуют различные стратегии смягчения проблемы. Самым непосредственным достижимым является объект Composite, который предлагает Павел (по сути, как С++ обрабатывает его). Я не знаю, является ли множественное наследование с помощью линеаризации C3 (или аналогичной) на картах для будущего Java, но я сомневаюсь.
Если ваш вопрос академичен, то правильным решением является то, что Птица и Лошадь более конкретны, и неверно предполагать, что Пегас - это просто Птица и Лошадь в сочетании. Правильнее было бы сказать, что у Пегаса есть определенные внутренние свойства, общие с птицами и лошадьми (то есть у них могут быть общие предки). Это может быть достаточно смоделировано, как указывает ответ Морица.
Ответ 9
Я думаю, что это сильно зависит от ваших потребностей и того, как ваши животные классы будут использоваться в вашем коде.
Если вы хотите использовать методы и особенности ваших реализаций Лошади и Птицы внутри вашего класса Pegasus, вы можете реализовать Pegasus как композицию Птицы и лошади:
public class Animals {
public interface Animal{
public int getNumberOfLegs();
public boolean canFly();
public boolean canBeRidden();
}
public interface Bird extends Animal{
public void doSomeBirdThing();
}
public interface Horse extends Animal{
public void doSomeHorseThing();
}
public interface Pegasus extends Bird,Horse{
}
public abstract class AnimalImpl implements Animal{
private final int numberOfLegs;
public AnimalImpl(int numberOfLegs) {
super();
this.numberOfLegs = numberOfLegs;
}
@Override
public int getNumberOfLegs() {
return numberOfLegs;
}
}
public class BirdImpl extends AnimalImpl implements Bird{
public BirdImpl() {
super(2);
}
@Override
public boolean canFly() {
return true;
}
@Override
public boolean canBeRidden() {
return false;
}
@Override
public void doSomeBirdThing() {
System.out.println("doing some bird thing...");
}
}
public class HorseImpl extends AnimalImpl implements Horse{
public HorseImpl() {
super(4);
}
@Override
public boolean canFly() {
return false;
}
@Override
public boolean canBeRidden() {
return true;
}
@Override
public void doSomeHorseThing() {
System.out.println("doing some horse thing...");
}
}
public class PegasusImpl implements Pegasus{
private final Horse horse = new HorseImpl();
private final Bird bird = new BirdImpl();
@Override
public void doSomeBirdThing() {
bird.doSomeBirdThing();
}
@Override
public int getNumberOfLegs() {
return horse.getNumberOfLegs();
}
@Override
public void doSomeHorseThing() {
horse.doSomeHorseThing();
}
@Override
public boolean canFly() {
return true;
}
@Override
public boolean canBeRidden() {
return true;
}
}
}
Другая возможность заключается в использовании Entity-Component-System вместо наследования для определения ваших животных. Конечно, это означает, что у вас не будет отдельных классов Java для животных, но вместо этого они определяются только их компонентами.
Некоторый псевдокод для подхода Entity-Component-System может выглядеть следующим образом:
public void createHorse(Entity entity){
entity.setComponent(NUMER_OF_LEGS, 4);
entity.setComponent(CAN_FLY, false);
entity.setComponent(CAN_BE_RIDDEN, true);
entity.setComponent(SOME_HORSE_FUNCTIONALITY, new HorseFunction());
}
public void createBird(Entity entity){
entity.setComponent(NUMER_OF_LEGS, 2);
entity.setComponent(CAN_FLY, true);
entity.setComponent(CAN_BE_RIDDEN, false);
entity.setComponent(SOME_BIRD_FUNCTIONALITY, new BirdFunction());
}
public void createPegasus(Entity entity){
createHorse(entity);
createBird(entity);
entity.setComponent(CAN_BE_RIDDEN, true);
}
Ответ 10
Ehm, ваш класс может быть подклассом только для одного другого, но все же вы можете иметь столько интерфейсов, сколько хотите.
Пегас на самом деле является лошадью (это особый случай лошади), который способен летать (что является "навыком" этой особой лошади). С другой стороны, вы можете сказать, что Pegasus - это птица, которая может ходить и 4legged - все зависит от того, как вам легче писать код.
Как и в вашем случае, вы можете сказать:
abstract class Animal {
private Integer hp = 0;
public void eat() {
hp++;
}
}
interface AirCompatible {
public void fly();
}
class Bird extends Animal implements AirCompatible {
@Override
public void fly() {
//Do something useful
}
}
class Horse extends Animal {
@Override
public void eat() {
hp+=2;
}
}
class Pegasus extends Horse implements AirCompatible {
//now every time when your Pegasus eats, will receive +2 hp
@Override
public void fly() {
//Do something useful
}
}
Ответ 11
вы можете иметь иерархию интерфейса, а затем расширить свои классы из выбранных интерфейсов:
public interface IAnimal {
}
public interface IBird implements IAnimal {
}
public interface IHorse implements IAnimal {
}
public interface IPegasus implements IBird,IHorse{
}
а затем определите свои классы по мере необходимости, расширив определенный интерфейс:
public class Bird implements IBird {
}
public class Horse implements IHorse{
}
public class Pegasus implements IPegasus {
}
Ответ 12
Интерфейсы не моделируют множественное наследование. Создатели Java считали, что множественное наследование неверно, поэтому на Java нет такой вещи.
Если вы хотите объединить функциональность двух классов в единую композицию объекта. То есть.
public class Main {
private Component1 component1 = new Component1();
private Component2 component2 = new Component2();
}
И если вы хотите выставить определенные методы, определите их и позвольте им делегировать вызов соответствующему контроллеру.
Здесь интерфейсы могут пригодиться - если Component1
реализует интерфейс Interface1
и Component2
реализует Interface2
, вы можете определить
class Main implements Interface1, Interface2
Таким образом, вы можете использовать объекты взаимозаменяемо, где это позволяет контекст.
Итак, с моей точки зрения, вы не можете попасть в проблему с алмазами.
Ответ 13
Как вам уже известно, множественное наследование классов в Java невозможно, но возможно с интерфейсами. Вы также можете рассмотреть возможность использования шаблона дизайна композиции.
Я написал очень всеобъемлющую статью о составе несколько лет назад...
https://codereview.stackexchange.com/questions/14542/multiple-inheritance-and-composition-with-java-and-c-updated
Ответ 14
Чтобы решить проблему взаимного наследования в Java → интерфейс используется
Заметки J2EE (core JAVA) Г-н K.V.R Page 51
День - 27
- Интерфейсы в основном используются для разработки пользовательских типов данных.
- В отношении интерфейсов мы можем достичь концепции множественных наследований.
- С интерфейсами мы можем достичь концепции полиморфизма, динамической привязки и, следовательно, мы можем улучшить производительность JAVA-программы в оборотов пространства памяти и времени выполнения.
Интерфейс представляет собой конструкцию, содержащую набор чисто undefined методы или интерфейс представляют собой набор чисто абстрактных Методы.
[...]
День - 28:
Синтаксис-1 для повторного использования функций интерфейса (ов) для класса:
[abstract] class <clsname> implements <intf 1>,<intf 2>.........<intf n>
{
variable declaration;
method definition or declaration;
};
В приведенном выше синтаксисе clsname представляет имя класса, который является наследуя функции от n номеров интерфейсов. "Реализация ключевое слово, которое используется для наследования функций интерфейса (ов) производный класс.
[...]
Синтаксис-2 наследует 'n количество интерфейсов к другому интерфейсу:
interface <intf 0 name> extends <intf 1>,<intf 2>.........<intf n>
{
variable declaration cum initialization;
method declaration;
};
[...]
Синтаксис-3:
[abstract] class <derived class name> extends <base class name> implements <intf 1>,<intf 2>.........<intf n>
{
variable declaration;
method definition or declaration;
};
Ответ 15
Чтобы уменьшить сложность и упростить язык, множественное наследование не поддерживается в java.
Рассмотрим сценарий, где A, B и C - три класса. Класс C наследует классы A и B. Если классы A и B имеют один и тот же метод, и вы вызываете его из объекта дочернего класса, будет неоднозначно вызывать метод класса A или B.
Поскольку ошибки времени компиляции лучше, чем ошибки времени выполнения, java отображает ошибку времени компиляции, если вы наследуете 2 класса. Итак, есть ли у вас один и тот же метод или другой, теперь будет ошибка времени компиляции.
class A {
void msg() {
System.out.println("From A");
}
}
class B {
void msg() {
System.out.println("From B");
}
}
class C extends A,B { // suppose if this was possible
public static void main(String[] args) {
C obj = new C();
obj.msg(); // which msg() method would be invoked?
}
}
Ответ 16
- Определите интерфейсы для определения возможностей. Вы можете определить несколько интерфейсов для нескольких возможностей. Эти возможности могут быть реализованы с помощью специальных Животных или Птицы.
- Используйте наследование, чтобы установить отношения между классами, используя нестатические и непубличные данные/методы.
- Используйте Decorator_pattern, чтобы динамически добавлять возможности. Это позволит вам уменьшить количество классов и комбинаций наследования.
Посмотрите ниже пример для лучшего понимания
Когда использовать шаблон декоратора?