Множинне спадкування Java


168

У спробі повністю зрозуміти, як вирішити кілька питань спадкування Java, у мене є класичне питання, яке мені потрібно уточнити.

Припустимо , у мене є клас Animalце має вкладені класи Birdі Horseі мені потрібно зробити клас , Pegasusякий простягається від Birdі Horseтак Pegasusє як птах і кінь.

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

Мені було цікаво, чи є інший спосіб вирішити проблему, за якої я ще можу створювати предмети для птахів та коней. Якби був спосіб створити тварин також, це було б чудово, але не обов'язково.


6
Я думаю, ви можете створити класи вручну та зберігати їх як члени (композиція замість спадкування). Для класу Proxy ( docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html ) це може бути варіантом, хоча вам також знадобляться інтерфейси.
Габор Бакос

4
@RAM тоді Bird не повинен поширювати Bird, а мати поведінку, яка дозволить йому здійснити політ. : D проблема вирішена
Йогеш

11
Саме так. Як щодо інтерфейсу CanFly. :-)
Здивований кокос

28
Я думаю, що це неправильний підхід. У вас є тварини - Коні, Птахи. А у вас є властивості - Летячий, Хижий. Пегас - не Кінь, це Кінь, яка може літати. Властивості повинні бути в інтерфейсах. Отже public class Pegasus extends Horse implements Flying.
Борис Павук

12
Я розумію, чому ви вважаєте, що це неправильно, і він не дотримується правил біології, і я ціную вашу стурбованість, але щодо програми, яку я повинен будувати, яка насправді стосується банків, це був найкращий підхід для мене. Оскільки я не хотів публікувати свої фактичні проблеми, оскільки це було б проти правил, я трохи змінив приклад. Дякую, хоча ...
Шелі

Відповіді:


115

Ви можете створити інтерфейси для занять на тваринах (клас у біологічному значенні), наприклад, 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). Я також не рекомендую використовувати імена інтерфейсів, починаючи з великої літери "Я", наприклад IBird, яка просто нічого не говорить про те, для чого вам потрібен інтерфейс. У цьому полягає відмінність питання: побудуйте ієрархію спадкування за допомогою інтерфейсів, використовуйте абстрактні класи, коли це корисно, застосуйте конкретні класи, де це необхідно, та використовуйте делегування, якщо це доречно.


9
Що ... саме те, що в ОП говорили, що вони знають, що ви можете зробити в Q.
Брайан Роуч,

4
Оскільки Pegasus IS вже Кінь (що летить), я думаю, ви могли б використати більше коду, якщо розширити Horse та реалізувати Avialae.
Пабло Лозано

8
Я не впевнений, я ще не бачив Пегаса. Однак у такому випадку я вважаю за краще скористатися знаряддям AbstractHorse, яке також можна використовувати для створення зебр або інших конячих тварин.
Моріц Петерсен

5
@MoritzPetersen Якщо ви дійсно хочете зайнятися повторним використанням абстракцій та даванням значущих імен, напевно, це AbstractEquidaeбуло б більше підходящим, ніж AbstractHorse. Було б дивно, щоб зебра витягнула абстрактного коня. Гарна відповідь, до речі.
afsantos

3
Впровадження типізації качок передбачало б також Duckреалізацію класу Avialae?
mgarciaisaia

88

Існує два основні підходи до об'єднання об'єктів разом:

  • Перший - спадкування . Оскільки ви вже визначили, що обмеження спадкування означає, що ви не можете тут робити те, що вам потрібно.
  • Другий - Склад . Оскільки спадкування не вдалося, вам потрібно використовувати склад.

Як це працює, це те, що у вас є об’єкт Animal. Потім всередині цього об'єкта ви додаєте інші об'єкти, які надають потрібні вам властивості та поведінку.

Наприклад:

  • Птах розширює Тваринний інвентар IFlier
  • Кінь поширює тваринні знаряддя IHerbivore, IQuadruped
  • Pegasus розширює Тварина знаряддя 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, а також можете використовувати всі інші переваги поліморфізму тощо.

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

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

Альтернативний підхід, залежно від того, що і як ви робите, полягає в тому, щоб Animalбазовий клас містив внутрішню колекцію для збереження списку різних видів поведінки. У такому випадку ви в кінцевому підсумку використовуєте щось ближче до шаблону стратегії. Це дає переваги з точки зору спрощення коду (наприклад Horse, нічого про це не потрібно знати Quadrupedабо Herbivore), але якщо ви також не підходите до інтерфейсу, ви втрачаєте багато переваг поліморфізму тощо.


Аналогічна альтернатива може також використовувати класи типів, хоча для використання в Java це не так природно (ви повинні використовувати методи перетворення тощо), це вступ може бути корисним для отримання ідеї: typeclassopedia.bitbucket.org
Gábor Bakos

1
Це набагато краще рішення, ніж підхід, рекомендований у прийнятій відповіді. Наприклад, якщо пізніше я хочу додати "дзьобний колір" до інтерфейсу Bird, у мене виникла проблема. Пегас - це композит, який бере елементи коней та птахів, але не повністю коней, ані птахів. Використання композиції має ідеальний сенс у цьому сценарії.
JDB досі пам’ятає Моніку

Але getFlier()має бути доповнене для кожного виду птахів.
SMUsamaShah

1
@ LifeH2O Розбийте їх на блоки спільної функціональності, а потім надайте їм це. тобто у вас може бути MathsTeacherі EnglishTeacherяк успадкування Teacher, ChemicalEngineer, і MaterialsEngineerт.д. успадкують Engineer. Teacherі Engineerобидва реалізують Component. PersonТоді просто є список Componentз, і ви можете дати їм правильні ComponentS для цього Person. тобто person.getComponent(Teacher.class), person.getComponent(MathsTeacher.class)і т. д.
Тім Б

1
Це найкраща відповідь. Як правило, успадкування являє собою "є", а інтерфейс представляє "може". Пегас - це тварина, яка вміє літати і ходити. Птах - тварина, яка вміє літати. Кінь - тварина, яка може ходити.
Robear

43

У мене дурна думка:

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

24
Це спрацює, але мені не подобається такий підхід (обгортка), тому що тоді, здається, Пегас ВІДКРИТЬ коня, замість цього він є конем.
Пабло Лозано

Дякую. Я не міг не опублікувати ідею. Я якось знаю, що це дурно, але я не бачив ЧОМУ це
дурно

3
Це майже як неперероблена версія композиційного підходу з відповіді Тіма Б.
Стефан

1
Це більш-менш прийнятий спосіб зробити це, хоча ви також повинні мати щось на зразок IJumps з методом «стрибка», який реалізується «Кінь» і «Пегас» та «ІФлії» методом «мухи», який реалізують Bird and Pegasus.
MatsT

2
@Pablo ні, Pegasus HAS horseFeatures та HAS birdFeatures. +1 для відповіді, оскільки він зберігає код простим, на відміну від незграбних, класичних нерестових, правильних Java-рішень.
JaneGoodall

25

Чи можу я запропонувати концепцію типу качки ?

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

Така концепція використовується у Стратегії . Наведений приклад насправді показує, як качка успадковує FlyBehaviourі, як QuackBehaviourі раніше, можуть бути качки, наприклад RubberDuck, які не можуть літати. Вони також могли зробити Duckрозширений Birdклас, але тоді вони відмовилися б від гнучкості, тому що кожен Duckміг би літати, навіть бідний RubberDuck.


19

Технічно кажучи, ви можете розширювати лише один клас за один раз і реалізовувати декілька інтерфейсів, але, покладаючи руки на розробку програмного забезпечення, я б скоріше запропонував вирішити проблему, яка, як правило, не відповідає. До речі, добре OO практика, не поширювати конкретні класи / лише розширювати абстрактні класи, щоб запобігти небажаній спадковій поведінці - не існує такого поняття, як "тварина" і не використовує тваринного предмета, а лише конкретних тварин.


13

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


1
Будьте в курсі, що якщо у Ваших Птахів та Коня є метод за замовчуванням, ви все одно будете стикатися з проблемою алмазів, і вам доведеться реалізувати її окремо у вашому класі Pegasus (або отримати помилку компілятора).
Mikkel Løkke

@Mikkel Løkke: Клас Пегас повинен визначити переопределення для неоднозначних методів, але може реалізувати їх, просто делегуючи або супер метод (або обидва в обраному порядку).
Холгер

12

Безпечно тримати коня в стайні з половинними дверима, так як кінь не може перебратися на півдвер. Тому я налаштовую службу житла для коней, яка приймає будь-який предмет типу коня і ставить його в стайню з напівдверцятами.

То чи така кінь, як тварина, здатна літати навіть коня?

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

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

АБО

Якщо він схожий на качку і трясеться як качка, але йому потрібні батарейки, ймовірно, ви маєте неправильну абстракцію .


Якщо я правильно прочитав вашу аналогію, ви говорите, що на початку інтерфейси здаються чудовими, оскільки вони дозволяють вам вирішити проблеми із дизайном, наприклад, ви можете використовувати чужий API, змушуючи класи через їхні інтерфейси. Але через кілька років ви зрозуміли, що проблема була в поганому дизайні.
CS

8

Java не має проблеми з множинним успадкуванням, оскільки не має багаторазового успадкування. Це задумано, щоб вирішити справжню задачу множинної спадковості (алмазна проблема).

Існують різні стратегії пом'якшення проблеми. Найбільш одразу досяжним є композиційний об'єкт, який пропонує Павло (по суті, як C ++ поводиться з ним). Я не знаю, чи є багатократне успадкування через лінеаризацію C3 (або подібне) на картках для майбутнього Java, але я сумніваюся в цьому.

Якщо ваше питання є академічним, то правильне рішення полягає в тому, що Птах і Кінь більш конкретні, і помилково вважати, що Пегас - це просто Птах і Кінь разом. Правильніше було б сказати, що пегас має певні властиві властивості спільно з птахами та конями (тобто вони, можливо, мають спільних предків). Це можна досить змоделювати, як вказує відповідь Моріца.


6

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

Якщо ви хочете мати можливість використовувати методи та особливості вашої реалізації коней та птахів всередині вашого класу 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);
}

4

ви можете мати ієрархію інтерфейсів, а потім розширити свої класи з обраних інтерфейсів:

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

1
Або він може просто: Громадський клас Пегас розширює коней для тварин, птах
Бетмен

ОП вже знає про це рішення, він шукає альтернативний спосіб зробити це
Йогеш

@ Batman, звичайно, він може, але якщо він хоче розширити ієрархію, йому потрібно буде дотримуватися такого підходу
richardtz

IBirdі IHorseповинні впроваджувати IAnimalзамістьAnimal
oliholz

@Yogesh, ти маєш рацію. Я не помітив місця, де він це констатує. Як "новачок", що мені робити зараз, видалити відповідь або залишити її там? Спасибі
richardtz

4

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

Пегас насправді є конем (це особливий випадок коня), який здатний літати (що є «майстерністю» цього особливого коня). З іншого боку, можна сказати, Пегас - це птах, який може ходити, і чотирилапий - все залежить, наскільки вам простіше написати код.

Як і у вашому випадку, ви можете сказати:

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

3

Інтерфейси не імітують багатократне успадкування. Творці 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 була розроблена як легка мова для підбору. Не складайте речі і не сподівайтесь на краще, це поле знань, а не здогадки.
Гімбі

3

Як ви вже знаєте, багаторазове успадкування класів на Java неможливо, але це можливо з інтерфейсами. Ви також можете розглянути можливість використання структури дизайну композиції.

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

/codereview/14542/multiple-inheritance-and-composition-with-java-and-c-updated


3
  1. Визначте інтерфейси для визначення можливостей. Ви можете визначити кілька інтерфейсів для декількох можливостей. Ці можливості можуть бути реалізовані конкретними тваринами або птахами .
  2. Використовуйте успадкування для встановлення зв’язків між класами шляхом обміну нестатичними та непублічними даними / методами.
  3. Використовуйте Decorator_pattern, щоб динамічно додавати можливості. Це дозволить вам зменшити кількість класів успадкування та комбінацій.

Перегляньте приклад нижче для кращого розуміння

Коли використовувати візерунок декоратора?


2

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

2

Для вирішення проблеми наслідування мутилів використовується Java → інтерфейс

Примітки J2EE (ядро 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' кількості інтерфейсів. '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;
};
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.