Многократное наследование Java
В попытке полностью понять, как решить проблемы множественного наследования в Java, у меня есть классический вопрос, который мне нужно прояснить.
Допустим, у меня есть класс Animal
это имеет подклассы Bird
а также Horse
и мне нужно сделать класс Pegasus
который простирается от Bird
а также Horse
поскольку Pegasus
это и птица, и лошадь.
Я думаю, что это классическая проблема с бриллиантами. Из того, что я могу понять, классический способ решения этой проблемы состоит в том, чтобы сделать Animal
, Bird
а также Horse
классы интерфейсов и реализации Pegasus
от них.
Мне было интересно, есть ли другой способ решить проблему, в которой я все еще могу создавать объекты для птиц и лошадей. Если бы существовал способ создавать животных, это было бы замечательно, но не обязательно.
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), а больше поведения (другие ссылаются на типирование утки, что тоже хорошо, но я имею в виду просто: биологический класс птиц, Avialae). Я также не рекомендую использовать имена интерфейсов, начинающиеся с заглавной буквы "I", например IBird
, который просто ничего не говорит о том, зачем вам нужен интерфейс. В этом отличие вопроса: создайте иерархию наследования с использованием интерфейсов, используйте абстрактные классы, когда это полезно, реализуйте конкретные классы, где это необходимо, и используйте делегирование, если это необходимо.
Существует два основных подхода к объединению объектов:
- Первое - это Наследование. Как вы уже определили, ограничения наследования означают, что вы не можете делать то, что вам нужно здесь.
- Второе - Композиция. Поскольку наследование не удалось, вам нужно использовать композицию.
Это работает так, что у вас есть объект Animal. Внутри этого объекта вы затем добавляете дополнительные объекты, которые задают свойства и поведение, которые вам требуются.
Например:
- Птица расширяет Животные орудия IFlier
- Лошадь расширяет Животные орудия IHerbivore, IQuadruped
- Пегас расширяет 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
- с таким большим контролем, как вам нужно, чтобы установить каждый бит.
Стратегия Pattern альтернативный подход к композиции
Альтернативный подход в зависимости от того, что и как вы делаете, это иметь 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();
}
}
Могу я предложить концепцию Duck-typing?
Скорее всего, вы бы хотели, чтобы Pegasus расширял интерфейс Bird и Horse, но типирование утки фактически предполагает, что вы должны скорее наследовать поведение. Как уже говорилось в комментариях, пегас не птица, но он может летать. Так что ваш Пегас должен скорее наследовать Flyable
-интерфейс и скажем Gallopable
-интерфейс.
Этот вид концепции используется в шаблоне стратегии. Данный пример показывает, как утка наследует FlyBehaviour
а также QuackBehaviour
и все же могут быть утки, например RubberDuck
, который не может летать. Они могли бы также сделать Duck
продлить Bird
-класс, но тогда они отказались бы от некоторой гибкости, потому что каждый Duck
сможет летать даже бедный RubberDuck
,
Технически говоря, вы можете расширять только один класс за раз и реализовывать несколько интерфейсов, но при разработке программного обеспечения я предпочел бы предложить решение для конкретной проблемы, которое обычно не отвечает. Кстати, это хорошая ОО-практика - не расширять конкретные классы / только расширять абстрактные классы для предотвращения нежелательного наследования - нет такого понятия, как "животное" и использование объекта животного, а только конкретных животных.
В Java 8, которая все еще находится на стадии разработки по состоянию на февраль 2014 года, можно использовать методы по умолчанию для достижения своего рода C++- например, множественного наследования. Вы также можете взглянуть на этот учебник, который показывает несколько примеров, с которыми легче начать работать, чем с официальной документацией.
Держать лошадь в конюшне с половиной двери безопасно, поскольку лошадь не может пройти через половину двери. Поэтому я настраиваю службу содержания лошадей, которая принимает любой предмет типа лошади и помещает его в конюшню с половиной двери.
Так похожа ли лошадь на животное, способное управлять даже лошадью?
Раньше я много думал о множественном наследовании, однако теперь, когда я занимаюсь программированием более 15 лет, я больше не беспокоюсь о реализации множественного наследования.
Чаще всего, когда я пытался справиться с дизайном, который указывал на множественное наследование, я позже приходил к выводу, что я упустил понимание проблемной области.
ИЛИ ЖЕ
У Java нет проблемы множественного наследования, поскольку у него нет множественного наследования. Это сделано для того, чтобы решить реальную проблему множественного наследования (проблема с алмазом).
Существуют разные стратегии смягчения проблемы. Наиболее быстро достижимым является объект Composite, который предлагает Павел (по сути, как C++ справляется с этим). Я не знаю, есть ли множественное наследование через линеаризацию C3 (или подобное) на картах для будущего Java, но я сомневаюсь в этом.
Если ваш вопрос носит академический характер, то правильное решение состоит в том, что Птица и Лошадь более конкретны, и неверно полагать, что Пегас - это просто Птица и Лошадь вместе взятые. Правильнее было бы сказать, что у Пегаса есть определенные внутренние свойства, общие с Птицами и Лошадями (то есть у них могут быть общие предки). Это может быть достаточно смоделировано, как указывает ответ Морица.
Я думаю, что это очень сильно зависит от ваших потребностей и того, как ваши классы животных будут использоваться в вашем коде.
Если вы хотите иметь возможность использовать методы и функции ваших реализаций Horse и Bird внутри вашего класса Pegasus, то вы можете реализовать Pegasus как композицию Bird и Horse:
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);
}
Э-э, ваш класс может быть подклассом только для одного другого, но, тем не менее, вы можете реализовать столько интерфейсов, сколько пожелаете.
Пегас на самом деле является лошадью (это особый случай лошади), которая способна летать (что является "навыком" этой особой лошади). С другой стороны, вы можете сказать, что Пегас - это птица, которая может ходить, и она легкая - все зависит от того, насколько вам легче писать код.
Как и в вашем случае вы можете сказать:
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
}
}
Вы можете иметь иерархию интерфейсов, а затем расширять ваши классы из выбранных интерфейсов
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 {
}
- Определите интерфейсы для определения возможностей. Вы можете определить несколько интерфейсов для нескольких возможностей. Эти возможности могут быть реализованы конкретным животным или птицей.
- Используйте наследование для установления отношений между классами путем совместного использования нестатических и закрытых данных / методов.
- Используйте Decorator_pattern для динамического добавления возможностей. Это позволит вам уменьшить количество классов наследования и комбинаций.
Посмотрите на приведенный ниже пример для лучшего понимания
Интерфейсы не имитируют множественное наследование. Создатели 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 невозможно, но возможно с интерфейсами. Вы также можете рассмотреть возможность использования шаблона дизайна композиции.
Я написал очень полную статью о композиции несколько лет назад...
Чтобы уменьшить сложность и упростить язык, множественное наследование не поддерживается в 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 (ядро JAVA) Примечания г-на KVR Стр. 51
День - 27
- Интерфейсы в основном используются для разработки пользовательских типов данных.
- Что касается интерфейсов, мы можем достичь концепции множественного наследования.
- С помощью интерфейсов мы можем реализовать концепцию полиморфизма, динамического связывания и, следовательно, мы можем улучшить производительность JAVA-программы за счет увеличения объема памяти и времени выполнения.
Интерфейс представляет собой конструкцию, которая содержит набор чисто неопределенных методов, или интерфейс представляет собой набор чисто абстрактных методов.
[...]
День - 28:
Синтаксис-1 для повторного использования функций интерфейса (ов) для класса:
[abstract] class <clsname> implements <intf 1>,<intf 2>.........<intf n> { variable declaration; method definition or declaration; };
В приведенном выше синтаксисе clsname представляет имя класса, который наследует функции от числа n интерфейсов. "Implements" - это ключевое слово, которое используется для наследования функций интерфейса (ов) для производного класса.
[...]
Синтаксис-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; };
Проблема не решена. Чтобы достаточно смоделировать это и предотвратить репликацию кода, вам понадобится множественное наследование или миксины. Интерфейсов с функциями по умолчанию недостаточно, потому что вы не можете удерживать элементы в интерфейсах. Моделирование интерфейса приводит к репликации кода в подклассах или статике, что является злом.
Все, что вы можете сделать, это использовать нестандартную конструкцию, разбить ее на несколько компонентов и собрать все вместе...
язык игрушек