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


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

допустим у меня есть класс Animal Это имеет подклассы Bird и Horse и мне нужно сделать класс Pegasus, начиная Bird и Horse С Pegasus - это как птица и лошадь.

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

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

16 160

16 ответов:

вы можете создать интерфейсы для классов животных (класс в биологическом смысле), такие как 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 {}

обновление

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

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

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

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

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

например:

  • птица выходит животные осуществляет IFlier
  • конь выходит животные осуществляет IHerbivore, IQuadruped
  • Пегас выходит животные осуществляет 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) но если вы также не делаете подход к интерфейсу, вы теряете много преимуществ полиморфизма и т. д.

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

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();
  }
}

могу я предложить концепцию утка-типирование?

скорее всего, вы бы хотели, чтобы Пегас расширил интерфейс птицы и лошади, но ввод утки на самом деле предполагает, что вы должны скорее наследовать поведение. Как уже говорилось в комментариях, Пегас не птица, но он может летать. Так что ваш Пегас должен скорее наследовать Flyable-интерфейс и позволяет сказать Gallopable-интерфейс.

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

Технически говоря, вы можете расширить только один класс за раз и реализовать несколько интерфейсов, но при наложении рук на разработку программного обеспечения я бы предпочел предложить конкретное решение проблемы, которое обычно не отвечает. Кстати, это хорошая ОО практика,не чтобы расширить конкретные классы / только расширить абстрактные классы, чтобы предотвратить нежелательное поведение наследования - нет такой вещи, как" животное", и нет использования животного объекта, но только конкретных животных.

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

Так это лошадь, как животное, которое может летать даже лошадь?

раньше я много думал о множественном наследовании, однако теперь, когда я программирую уже более 15 лет, я больше не забочусь о реализации нескольких наследование.

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

или

если оно выглядит как утка и крякает как утка, но он должен батареи, у вас, наверное, неправильная абстракция.

в Java 8, который все еще находится на стадии разработки по состоянию на февраль 2014 года, вы можете использовать методы по умолчанию для достижения своего рода C++-как множественное наследование. Вы также можете посмотреть в этом уроке который показывает несколько примеров, с которыми должно быть легче начать работать, чем с официальной документацией.

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

существуют различные стратегии для смягчения проблемы. Наиболее непосредственно достижимым является композитный объект, который предлагает Павел (по существу, как C++ обрабатывает его). Я не знаю, есть ли множественное наследование через линеаризацию C3 (или аналогичную) на картах для Java будущее, но я сомневаюсь в этом.

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

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

Если вы хотите иметь возможность использовать методы и функции ваших реализаций лошади и птицы внутри вашего класса 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;
        }
    }
}

еще одна возможность-использовать Сущность-Компонент-Система подход вместо наследования для определения зверята. Конечно, это означает, что у вас не будет отдельных классов 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);
}

вы можете иметь иерархию интерфейса, а затем расширить свои классы из выбранных интерфейсов:

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 {
}

Ehm, ваш класс может быть подклассом только для 1 другого, но все же вы можете иметь столько интерфейсов, сколько хотите.

Пегас на самом деле лошадь (это частный случай лошади), которая способна летать (что является "навыком" этой специальной лошади). С другой стороны, вы можете сказать, что Пегас - это птица, которая может ходить, и является 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
   }
}

интерфейсы не имитируют множественное наследование. Создатели Java считали множественное наследование неправильным, поэтому в Java нет такой вещи.

если вы хотите объединить функциональность двух классов в один-используйте композицию объектов. То есть

public class Main {
    private Component1 component1 = new Component1();    
    private Component2 component2 = new Component2();
}

и если вы хотите предоставить определенные методы, определите их и позвольте им делегировать вызов соответствующему контроллеру.

здесь интерфейсы могут пригодиться - если Component1 реализует интерфейс Interface1 и Component2 осуществляет Interface2, можно определить

class Main implements Interface1, Interface2

так что вы можете использовать объекты взаимозаменяемо, где контекст позволяет это.

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

Как вы уже знаете, множественное наследование классов в Java невозможно, но это возможно с интерфейсами. Вы также можете рассмотреть возможность использования Шаблона дизайна композиции.

Я написал очень подробную статью о композиции несколько лет назад...

https://codereview.stackexchange.com/questions/14542/multiple-inheritance-and-composition-with-java-and-c-updated

чтобы уменьшить сложность и упростить язык, множественное наследование не поддерживается в 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?  
    }
} 

для решения задачи наследования множества в Java → используется интерфейс

J2EE (core JAVA) Примечания г-на К. В. Р страница 51

день - 27

  1. интерфейсы в основном используются для разработки пользовательских типов данных.
  2. в отношении интерфейсов мы можем достичь концепции множественного наследования.
  3. С интерфейсами мы можем достичь концепция полиморфизма, динамического связывания и, следовательно, мы можем улучшить производительность JAVA-программы в витки пространства памяти и времени выполнения.

интерфейс-это конструкция, которая содержит коллекцию чисто неопределенные методы или интерфейс представляет собой набор чисто абстрактных методы.

[...]

день 28:

синтаксис-1 для повторного использования функций интерфейса класс:

[abstract] class <clsname> implements <intf 1>,<intf 2>.........<intf n>
{
    variable declaration;
    method definition or declaration;
};

В приведенном выше синтаксисе clsname представляет имя класса, который является наследование функций от ' n ' числа интерфейсов. ‘Реализует’ это ключевое слово, которое используется для наследования функций интерфейса(ов) к a производный класс.

[...]

синтаксис-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;
};
  1. определение интерфейсы для определения возможности. Вы можете определить несколько интерфейсов для различных возможностей. Эти возможности могут быть реализованы с помощью конкретных животные или птица.
  2. использовать наследование для установления отношений между классами путем совместного использования нестатических и непубличных данных / методов.
  3. использовать Decorator_pattern для динамического добавления возможностей. Это позволит вам уменьшите количество классов и комбинаций наследования.

посмотрите на ниже пример для лучшего понимания

когда использовать шаблон "декоратор"?