Java Multiple Ereditarietà

Nel tentativo di comprendere appieno come risolvere i problemi di ereditarietà multipla di Java, ho una domanda classica che ho bisogno di chiarire.

Diciamo che ho class Animal ha sottoclassi Bird e Horse e ho bisogno di fare un Pegasus class che si estende da Bird e Horse poiché Pegasus è sia un uccello che un cavallo.

Penso che questo sia il classico problema dei diamanti. Da quello che posso capire il modo classico per risolvere questo è quello di rendere le interfacce di classi Animal , Bird and Horse e implementare Pegasus da loro.

Mi stavo chiedendo se ci fosse un altro modo per risolvere il problema in cui posso ancora creare oggetti per uccelli e cavalli. Se ci fosse un modo per essere in grado di creare animali, sarebbe fantastico ma non necessario.

Potresti creare interfacce per classi di animali (class nel significato biologico), come public interface Equidae per cavalli e public interface Avialae per gli uccelli (non sono un biologo, quindi i termini potrebbero essere errati).

Quindi puoi ancora creare un

 public class Bird implements Avialae { } 

e

 public class Horse implements Equidae {} 

e anche

 public class Pegasus implements Avialae, Equidae {} 

Aggiungendo dai commenti:

Per ridurre il codice duplicato, puoi creare una class astratta che contiene la maggior parte del codice comune degli animali che desideri implementare.

 public abstract class AbstractHorse implements Equidae {} public class Horse extends AbstractHorse {} public class Pegasus extends AbstractHorse implements Avialae {} 

Aggiornare

Mi piacerebbe aggiungere un altro dettaglio. Come osserva Brian , questo è già qualcosa che l’OP sapeva.

Tuttavia, voglio sottolineare, che suggerisco di aggirare il problema di “multi-ereditarietà” con le interfacce e che non raccomando di utilizzare interfacce che rappresentano già un tipo concreto (come Bird) ma più un comportamento (altri si riferiscono a battere a macchina, anche questo è buono, ma intendo solo: la class biologica degli uccelli, Avialae). Inoltre, sconsiglio di utilizzare i nomi delle interfacce che iniziano con un ‘I’ IBird , come ad esempio IBird , che non dice nulla sul perché è necessaria un’interfaccia. Questa è la differenza rispetto alla domanda: build la gerarchia dell’ereditarietà utilizzando le interfacce, utilizzare classi astratte quando utili, implementare classi concrete ove necessario e utilizzare la delega, se necessario.

Esistono due approcci fondamentali per combinare gli oggetti:

  • Il primo è l’ ereditarietà . Come hai già identificato le limitazioni dell’ereditarietà significa che non puoi fare ciò di cui hai bisogno qui.
  • Il secondo è Composizione . Dal momento che l’ereditarietà non funziona, è necessario utilizzare la composizione.

Il modo in cui funziona è che hai un object Animale. All’interno di quell’object si aggiungono ulteriori oggetti che forniscono le proprietà e i comportamenti richiesti.

Per esempio:

  • Bird estende gli attrezzi animali IFlier
  • Horse extends Animal implementa IHerbivore, IQuadruped
  • Pegasus estende Animal implementa IHerbivore, IQuadruped, IFlier

Ora IFlier sembra proprio questo:

  interface IFlier { Flier getFlier(); } 

Quindi Bird presenta così:

  class Bird extends Animal implements IFlier { Flier flier = new Flier(); public Flier getFlier() { return flier; } } 

Ora hai tutti i vantaggi dell’eredità. Puoi riutilizzare il codice. Puoi avere una collezione di IFliers e puoi usare tutti gli altri vantaggi del polimorfismo, ecc.

Tuttavia, hai anche tutta la flessibilità di Composizione. Puoi applicare tante diverse interfacce e una class di supporto composita a tuo piacimento per ogni tipo di Animal – con tutto il controllo di cui hai bisogno su come ogni bit è impostato.

Strategia Approccio alternativo alla composizione

Un approccio alternativo che dipende da cosa e come si sta facendo è che la class di base Animal contenga una collezione interna per mantenere l’elenco di comportamenti diversi. In tal caso finisci per usare qualcosa di più vicino al modello di strategia. Ciò offre vantaggi in termini di semplificazione del codice (ad esempio, Horse non ha bisogno di sapere nulla su Quadruped o Herbivore ) ma se non si fa anche l’approccio di interfaccia si perdono molti dei vantaggi del polimorfismo, ecc.

Ho un’idea stupida:

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

Posso suggerire il concetto di Duck-typing ?

Molto probabilmente tenderesti a far sì che il Pegasus estendesse un’interfaccia Bird and a Horse ma digitando anatra in realtà suggerisci che dovresti piuttosto ereditare il comportamento . Come già affermato nei commenti, un pegaso non è un uccello ma può volare. Quindi il tuo Pegasus dovrebbe piuttosto ereditare un’interfaccia Flyable e dire Gallopable Gallopable.

Questo tipo di concetto è utilizzato nel modello di strategia . L’esempio dato mostra in realtà come un’anatra eredita il FlyBehaviour e QuackBehaviour e tuttavia ci possono essere anatre, ad esempio il RubberDuck , che non può volare. Avrebbero potuto anche fare in modo che l’ Duck estendesse una class Bird ma poi avrebbero rinunciato ad una certa flessibilità, perché ogni Duck sarebbe stata in grado di volare, anche il povero RubberDuck .

Dal punto di vista tecnico, è ansible estendere una sola class alla volta e implementare più interfacce, ma quando si mettono le mani sull’ingegneria del software, vorrei piuttosto suggerire che una soluzione specifica per il problema non sia generalmente rispondente. A proposito, è una buona pratica OO, non estendere le classi concrete / estendere solo le classi astratte per prevenire comportamenti ereditari indesiderati – non esiste un “animale” e nessun uso di un object animale ma solo di animali concreti.

È sicuro tenere un cavallo in una stalla con una mezza porta, dato che un cavallo non può superare una mezza porta. Perciò ho creato un servizio di custodia per cavalli che accetta qualsiasi object di tipo cavallo e lo mette in una stalla con una mezza porta.

Quindi un cavallo come un animale può volare anche a cavallo?

Pensavo spesso all’ereditarietà multipla, ma ora che lavoro da più di 15 anni, non mi interessa più implementare l’ereditarietà multipla.

Il più delle volte, quando ho cercato di far fronte a un progetto che puntava verso l’ereditarietà multipla, in seguito sono venuto a rilasciare che non avevo capito il dominio del problema.

O

Se sembra un’anatra e ciarlona come un’anatra ma ha bisogno di batterie, probabilmente hai l’astrazione sbagliata .

In Java 8, che è ancora in fase di sviluppo a partire da febbraio 2014, è ansible utilizzare i metodi predefiniti per ottenere una sorta di ereditarietà multipla simile a C ++. Puoi anche dare un’occhiata a questo tutorial che mostra alcuni esempi che dovrebbero essere più facili da usare rispetto alla documentazione ufficiale.

Java non ha un problema di ereditarietà multipla, poiché non ha ereditarietà multipla. Questo è in base alla progettazione, al fine di risolvere il vero problema dell’eredità multipla (il problema del diamante).

Esistono diverse strategie per mitigare il problema. Il più immediatamente realizzabile è l’object Composito suggerito da Pavel (essenzialmente come C ++ lo gestisce). Non so se l’ereditarietà multipla tramite la linearizzazione C3 (o simili) sia sulle carte per il futuro di Java, ma ne dubito.

Se la tua domanda è accademica, allora la soluzione corretta è che Uccello e Cavallo sono più concreti, ed è falso presumere che un Pegaso sia semplicemente un Uccello e un Cavallo messi insieme. Sarebbe più corretto dire che un Pegaso ha alcune proprietà intrinseche in comune con gli uccelli ei cavalli (cioè hanno forse antenati comuni). Questo può essere sufficientemente modellato come la risposta di Moritz.

Penso che dipenda molto dalle tue esigenze e dal modo in cui le tue classi animali devono essere utilizzate nel tuo codice.

Se vuoi essere in grado di utilizzare i metodi e le caratteristiche delle tue implementazioni Horse e Bird all’interno della tua class Pegasus, allora potresti implementare Pegasus come una composizione di un uccello e un cavallo:

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

Un’altra possibilità è usare un approccio di Entity-Component-System invece dell’ereditarietà per definire i tuoi animali. Ovviamente ciò significa che non si avranno classi Java individuali degli animali, ma che sono definiti solo dai loro componenti.

Qualche pseudo codice per un approccio di Entity-Component-System potrebbe assomigliare a questo:

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

puoi avere una gerarchia di interfacce e quindi estendere le tue classi dalle interfacce selezionate:

 public interface IAnimal { } public interface IBird implements IAnimal { } public interface IHorse implements IAnimal { } public interface IPegasus implements IBird,IHorse{ } 

e quindi definisci le tue classi secondo necessità, estendendo un’interfaccia specifica:

 public class Bird implements IBird { } public class Horse implements IHorse{ } public class Pegasus implements IPegasus { } 

Ehm, la tua class può essere la sottoclass solo per l’altra, ma ancora, puoi avere tante interfacce implementate, come desideri.

Un Pegaso è in effetti un cavallo (è un caso speciale di un cavallo), che è in grado di volare (che è l’abilità di questo cavallo speciale). D’altra parte, si può dire, il Pegaso è un uccello, che può camminare, ed è a 4 zampe – dipende tutto, in che modo è più facile scrivere il codice.

Come nel tuo caso, puoi dire:

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

Le interfacce non simulano l’ereditarietà multipla. I creatori di Java consideravano errata l’ereditarietà multipla, quindi non c’è nulla di simile in Java.

Se si desidera combinare la funzionalità di due classi in una composizione di oggetti monouso. ie

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

E se si desidera esporre determinati metodi, definirli e consentire loro di debind la chiamata al controller corrispondente.

Qui le interfacce possono tornare utili: se Component1 implementa l’interfaccia Interface1 e Component2 implementa Interface2 , è ansible definire

 class Main implements Interface1, Interface2 

In modo che tu possa usare gli oggetti in modo intercambiabile dove il contesto lo consente.

Quindi, dal mio punto di vista, non puoi entrare nel problema dei diamanti.

Come già saprai, l’ereditarietà multipla delle classi in Java non è ansible, ma è ansible con le interfacce. Si consiglia inoltre di prendere in considerazione l’utilizzo del modello di progettazione della composizione.

Ho scritto un articolo molto completo sulla composizione alcuni anni fa …

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

Per ridurre la complessità e semplificare la lingua, l’ereditarietà multipla non è supportata in java.

Considera uno scenario in cui A, B e C sono tre classi. La class C eredita le classi A e B. Se le classi A e B hanno lo stesso metodo e lo chiamate dall’object class figlio, ci sarà un’ambiguità per chiamare il metodo della class A o B.

Poiché gli errori di compilazione sono migliori degli errori di runtime, java esegue il rendering dell’errore di compilazione se si ereditano 2 classi. Quindi, se hai lo stesso metodo o altro, ora ci sarà un errore in fase di compilazione.

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

Per risolvere il problema dell’ereditarietà mutiple in Java viene utilizzata l’interfaccia →

J2EE (core JAVA) Note di Mr. KVR Pagina 51

Giorno 27

  1. Le interfacce sono fondamentalmente utilizzate per sviluppare tipi di dati definiti dall’utente.
  2. Per quanto riguarda le interfacce, possiamo raggiungere il concetto di eredità multiple.
  3. Con le interfacce possiamo raggiungere il concetto di polimorfismo, legame dinamico e quindi possiamo migliorare le prestazioni di un programma JAVA in termini di spazio di memoria e tempo di esecuzione.

Un’interfaccia è un costrutto che contiene la raccolta di metodi puramente indefiniti o un’interfaccia è una raccolta di metodi puramente astratti.

[…]

Giorno 28:

Sintassi-1 per riutilizzare le funzionalità dell’interfaccia (s) in class:

 [abstract] class  implements ,......... { variable declaration; method definition or declaration; }; 

Nella syntax precedente clsname rappresenta il nome della class che eredita le funzionalità dal numero ‘n’ di interfacce. ‘Implementa’ è una parola chiave che viene utilizzata per ereditare le caratteristiche dell’interfaccia (o delle interfacce) in una class derivata.

[…]

Sintassi-2 che eredita il numero “n” di interfacce su un’altra interfaccia:

 interface  extends ,......... { variable declaration cum initialization; method declaration; }; 

[…]

Sintassi-3:

 [abstract] class  extends  implements ,......... { variable declaration; method definition or declaration; }; 
  1. Definire le interfacce per definire le funzionalità. È ansible definire più interfacce per più funzionalità. Queste capacità possono essere implementate da specifici animali o uccelli .
  2. Utilizzare l’ ereditarietà per stabilire relazioni tra classi condividendo dati / metodi non statici e non pubblici.
  3. Usa Decorator_pattern per aggiungere funzionalità dynamicmente. Questo ti consentirà di ridurre il numero di classi e combinazioni di eredità.

Dai un’occhiata al seguente esempio per una migliore comprensione

Quando utilizzare il pattern Decorator?