Perché l'ereditarietà multipla non è consentita in Java o C #?


115

So che l'ereditarietà multipla non è consentita in Java e C #. Molti libri dicono semplicemente che l'ereditarietà multipla non è consentita. Ma può essere implementato utilizzando le interfacce. Non si discute del motivo per cui non è consentito. Qualcuno può dirmi esattamente perché non è consentito?


1
Voglio solo sottolineare che ci sono framework là fuori che consentono un comportamento simile a MI nelle classi C #. E ovviamente hai la possibilità di avere mixin senza stato (usando metodi di estensione) - essendo apolidi non sono molto utili però.
Dmitri Nesteruk

1
Che le interfacce abbiano qualcosa a che fare con l'ereditarietà è un'illusione creata dalla sintassi del linguaggio. L'eredità dovrebbe renderti più ricco. Niente del genere per un'interfaccia, non erediti lo squat e ti rende significativamente più povero. Hai ereditato il debito di gioco dello zio Harry, hai ancora molto lavoro da fare per implementare l'interfaccia.
Hans Passant

Risposte:


143

La risposta breve è: perché i progettisti del linguaggio hanno deciso di non farlo.

Fondamentalmente, sembrava che sia i progettisti .NET che Java non consentissero l'ereditarietà multipla perché ritenevano che l'aggiunta di MI aggiungesse troppa complessità ai linguaggi fornendo allo stesso tempo troppo poco vantaggio .

Per una lettura più divertente e approfondita, sono disponibili sul web alcuni articoli con interviste ad alcuni dei progettisti linguistici. Ad esempio, per .NET, Chris Brumme (che ha lavorato presso MS su CLR) ha spiegato i motivi per cui hanno deciso di non:

  1. In realtà lingue diverse hanno aspettative diverse su come funziona MI. Ad esempio, come vengono risolti i conflitti e se le basi duplicate vengono unite o ridondanti. Prima di poter implementare MI nel CLR, dobbiamo fare un sondaggio di tutte le lingue, capire i concetti comuni e decidere come esprimerli in modo linguistico neutro. Dovremmo anche decidere se MI appartiene al CLS e cosa significherebbe per i linguaggi che non vogliono questo concetto (presumibilmente VB.NET, per esempio). Ovviamente, questo è il business in cui ci troviamo come Common Language Runtime, ma non siamo ancora riusciti a farlo per MI.

  2. Il numero di posti in cui MI è veramente appropriato è in realtà piuttosto ridotto. In molti casi, l'ereditarietà di più interfacce può invece portare a termine il lavoro. In altri casi, potresti essere in grado di utilizzare l'incapsulamento e la delega. Se dovessimo aggiungere un costrutto leggermente diverso, come i mixin, sarebbe effettivamente più potente?

  3. L'ereditarietà di più implementazioni inietta molta complessità nell'implementazione. Questa complessità influisce su casting, layout, invio, accesso al campo, serializzazione, confronti di identità, verificabilità, riflessione, generici e probabilmente molti altri luoghi.

Puoi leggere l'articolo completo qui.

Per Java, puoi leggere questo articolo :

Le ragioni per omettere l'ereditarietà multipla dal linguaggio Java derivano principalmente dall'obiettivo "semplice, orientato agli oggetti e familiare". Essendo un linguaggio semplice, i creatori di Java volevano un linguaggio che la maggior parte degli sviluppatori potesse comprendere senza una formazione approfondita. A tal fine, hanno lavorato per rendere il linguaggio il più simile possibile al C ++ (familiare) senza trasferire l'inutile complessità del C ++ (semplice).

Secondo i progettisti, l'ereditarietà multipla causa più problemi e confusione di quanti ne risolva. Quindi tagliano l'ereditarietà multipla dalla lingua (proprio come tagliano il sovraccarico dell'operatore). La vasta esperienza C ++ dei progettisti ha insegnato loro che l'ereditarietà multipla non valeva il mal di testa.


10
Bel confronto. Piuttosto indicativo dei processi di pensiero che sono alla base di entrambe le piattaforme, credo.
CurtainDog

97

L'ereditarietà multipla dell'implementazione è ciò che non è consentito.

Il problema è che il compilatore / runtime non riesce a capire cosa fare se hai una classe Cowboy e una Artist, entrambe con implementazioni per il metodo draw (), e quindi provi a creare un nuovo tipo CowboyArtist. Cosa succede quando chiami il metodo draw ()? Qualcuno giace morto per strada o hai un bellissimo acquerello?

Credo che si chiami problema dell'ereditarietà del doppio diamante.


80
Ottieni un delizioso acquerello di qualcuno morto per strada :-)
Dan F

Questo è l'unico problema? Penso, non sono sicuro, che il C ++ risolva questo problema con la parola chiave virtuale, è vero? Non sono bravo in C ++
Abdulsattar Mohammed

2
In C ++ puoi specificare quale delle funzioni della classe base chiamare in derivato o reimplementarla tu stesso.
Dmitry Risenberg

1
Il design moderno tende a privilegiare la composizione rispetto all'eredità in tutti i casi tranne che nei casi più semplici. L'ereditarietà multipla non sarebbe mai stata considerata un caso semplice. Con la composizione, hai un controllo preciso su ciò che fa la tua classe quando ci sono problemi con i diamanti come questo ...
Bill Michell,

Avresti semplicemente bisogno di un elenco di priorità. Viene utilizzato nel sistema a oggetti di Common Lisp, CLOS. Tutte le classi formano un'eredità e il metodo chiamato è determinato da lì. Inoltre, CLOS consente di definire regole diverse in modo che un CowboyArtist.draw () possa disegnare prima un Cowboy e poi un Artista. O quello che hai mai inventato.
Sebastian Krog,

19

Motivo: Java è molto popolare e facile da codificare, grazie alla sua semplicità.

Quindi, qualunque cosa gli sviluppatori java si sentano difficile e complicato da capire per i programmatori, hanno cercato di evitarlo. Un tale tipo di proprietà è l'ereditarietà multipla.

  1. Hanno evitato i suggerimenti
  2. Hanno evitato l'ereditarietà multipla.

Problema con ereditarietà multipla: problema del diamante.

Esempio :

  1. Supponiamo che la classe A stia avendo un metodo fun (). la classe B e la classe C derivano dalla classe A.
  2. Ed entrambe le classi B e C sovrascrivono il metodo fun ().
  3. Ora supponi che la classe D erediti sia la classe B che C. (solo Assunzione)
  4. Crea oggetto per la classe D.
  5. D d = nuovo D ();
  6. e prova ad accedere a d.fun (); => chiamerà fun () della classe B o fun () della classe C?

Questa è l'ambiguità esistente nel problema dei diamanti.

Non è impossibile risolvere questo problema, ma crea più confusione e complessità al programmatore durante la lettura. Causa più problemi di quanti ne cerchi di risolvere.

Nota : in qualsiasi modo è sempre possibile implementare indirettamente l'ereditarietà multipla utilizzando le interfacce.


1
"Ma in qualsiasi modo è sempre possibile implementare indirettamente l'ereditarietà multipla utilizzando le interfacce." - che richiede al programmatore di specificare nuovamente il corpo dei metodi ogni volta, ancora e ancora.
Kari

13

Perché Java ha una filosofia di progettazione molto diversa da C ++. (Non ho intenzione di discutere di C # qui.)

Nella progettazione del C ++, Stroustrup voleva includere funzionalità utili, indipendentemente da come potevano essere utilizzate in modo improprio. È possibile rovinare tutto con ereditarietà multipla, sovraccarico dell'operatore, modelli e varie altre funzionalità, ma è anche possibile fare cose molto buone con loro.

La filosofia di progettazione Java è enfatizzare la sicurezza nei costrutti del linguaggio. Il risultato è che ci sono cose molto più scomode da fare, ma puoi essere molto più sicuro che il codice che stai guardando significhi ciò che pensi che faccia.

Inoltre, Java è stata in larga misura una reazione da C ++ e Smalltalk, i linguaggi OO più conosciuti. Ci sono molti altri linguaggi OO (Common Lisp è stato in realtà il primo ad essere standardizzato), con diversi sistemi OO che gestiscono meglio MI.

Senza contare che è del tutto possibile eseguire MI in Java, utilizzando interfacce, composizione e delega. È più esplicito che in C ++, e quindi è più goffo da usare, ma ti darà qualcosa che è più probabile che tu capisca a prima vista.

Non c'è una risposta giusta qui. Esistono diverse risposte e quale è la migliore per una data situazione dipende dalle applicazioni e dalle preferenze individuali.


12

Il motivo principale (anche se non l'unico) per cui le persone si allontanano da MI è il cosiddetto "problema dei diamanti" che porta all'ambiguità nella tua implementazione. Questo articolo di wikipedia ne discute e spiega meglio di quanto potrei. MI può anche portare a codice più complesso e molti progettisti OO affermano che non hai bisogno di MI, e se lo usi il tuo modello è probabilmente sbagliato. Non sono sicuro di essere d'accordo con quest'ultimo punto, ma mantenere le cose semplici è sempre un buon piano.


8

In C ++ l'ereditarietà multipla era un grosso problema se usata in modo improprio. Per evitare questi popolari problemi di progettazione, è stata invece forzata l '"ereditarietà" di più interfacce nei linguaggi moderni (java, C #).


8

L'ereditarietà multipla è

  • difficile da capire
  • difficile da eseguire il debug (ad esempio, se mescoli classi da più framework che hanno metodi con nomi identici in profondità, possono verificarsi sinergie abbastanza inaspettate)
  • facile uso improprio
  • non proprio così utile
  • difficile da implementare, soprattutto se vuoi che sia fatto in modo corretto ed efficiente

Pertanto, può essere considerata una scelta saggia non includere l'ereditarietà multipla nel linguaggio Java.


3
Non sono d'accordo con tutto quanto sopra, avendo lavorato con Common Lisp Object System.
David Thornley,

2
I linguaggi dinamici non contano ;-) In qualsiasi sistema simile a Lisp, hai un REPL che rende il debugging piuttosto semplice. Inoltre, CLOS (a quanto ho capito, non l'ho mai usato veramente, ne ho solo letto) è un sistema meta-oggetto, con molta flessibilità e un atteggiamento tutto tuo. Ma si consideri un linguaggio compilato staticamente come C ++, in cui il compilatore genera una ricerca di metodi diabolicamente complessa utilizzando più vtables (possibilmente sovrapposte): in un'implementazione di questo tipo, scoprire quale implementazione di un metodo è stata invocata potrebbe essere un'attività non così banale.
mfx

5

Un altro motivo è che l'ereditarietà singola rende il casting banale, non emettendo istruzioni assembler (oltre a verificare la compatibilità dei tipi dove richiesto). Se avessi l'ereditarietà multipla, dovresti capire dove inizia un determinato genitore nella classe figlio. Quindi le prestazioni sono sicuramente un vantaggio (anche se non l'unico).


4

Ai vecchi tempi (anni '70), quando l'informatica era più scienza e meno produzione di massa, i programmatori avevano il tempo di pensare a un buon design e una buona implementazione e di conseguenza i prodotti (programmi) avevano un'alta qualità (es. Progettazione TCP / IP e implementazione). Al giorno d'oggi, quando tutti programmano e i manager stanno cambiando le specifiche prima delle scadenze, problemi sottili come quello descritto nel collegamento di wikipedia dal post di Steve Haigh sono difficili da monitorare; pertanto, l '"ereditarietà multipla" è limitata dalla progettazione del compilatore. Se ti piace, puoi comunque usare C ++ ... e avere tutta la libertà che desideri :)


4
... inclusa la libertà di spararsi al piede, più volte;)
Mario Ortegón

4

Prendo l'affermazione che "l'ereditarietà multipla non è consentita in Java" con un pizzico di sale.

L'ereditarietà multipla viene definita quando un "Tipo" eredita da più di un "Tipo". E anche le interfacce sono classificate come tipi poiché hanno un comportamento. Quindi Java ha ereditarietà multipla. Solo che è più sicuro.


6
Ma non erediti da un'interfaccia, la implementi. Le interfacce non sono classi.
Blorgbeard uscirà il

2
Sì, ma l'eredità non è mai stata definita in modo così ristretto, tranne che in alcuni libri preliminari. E penso che dovremmo guardare oltre la grammatica. Entrambi fanno la stessa cosa e le interfacce sono state "inventate" solo per questo motivo.
Rig Veda

Prendi l'interfaccia Closeable: un metodo, close (). Supponiamo che tu voglia estenderlo, a Openable: ha un metodo open () che imposta un campo booleano isOpen su true. Il tentativo di aprire un Openable che è già aperto genera un'eccezione, così come il tentativo di chiudere un Openable che non è aperto ... Centinaia di genealogie di classi più interessanti potrebbero voler adottare tale funzionalità ... senza dover scegliere di estendere ciascuna tempo dalla classe Openable. Se Openable fosse semplicemente un'interfaccia non potrebbe fornire questa funzionalità. QED: le interfacce non forniscono ereditarietà!
mike rodent

4

Il caricamento dinamico delle classi rende difficile l'implementazione dell'ereditarietà multipla.

In Java in realtà hanno evitato la complessità dell'ereditarietà multipla invece utilizzando l'ereditarietà e l'interfaccia singola. La complessità dell'ereditarietà multipla è molto alta in una situazione come spiegata di seguito

problema del diamante di eredità multipla. Abbiamo due classi B e C che ereditano da A. Supponiamo che B e C stiano sovrascrivendo un metodo ereditato e forniscono la propria implementazione. Ora D eredita sia da B che da C facendo ereditarietà multipla. D dovrebbe ereditare quel metodo sovrascritto, jvm non può decidere quale metodo sovrascritto verrà utilizzato?

In c ++ le funzioni virtuali vengono utilizzate per gestire e dobbiamo farlo in modo esplicito.

Questo può essere evitato utilizzando le interfacce, non ci sono corpi di metodo. Le interfacce non possono essere istanziate: possono essere implementate solo da classi o estese da altre interfacce.


3

In realtà l'ereditarietà multipla sorgerà dalla complessità se le classi ereditate hanno la stessa funzione. cioè il compilatore avrà una confusione su quale deve essere scelto (problema del diamante). Quindi in Java quella complessità è stata rimossa e ha fornito l'interfaccia per ottenere le funzionalità come l'ereditarietà multipla fornita. Possiamo usare l'interfaccia


2

Java ha un concetto, cioè il polimorfismo. Esistono 2 tipi di polimorfismo in java. Sono disponibili il sovraccarico del metodo e l'override del metodo. Tra questi, l'override del metodo avviene con relazioni di super e sottoclassi. Se stiamo creando un oggetto di una sottoclasse e invocando il metodo della superclasse, e se la sottoclasse estende più di una classe, quale metodo della superclasse dovrebbe essere chiamato?

Oppure, chiamando il costruttore della superclasse di super() , quale costruttore della super classe verrà chiamato?

Queste decisioni sono impossibili dalle attuali funzionalità dell'API Java. quindi l'ereditarietà multipla non è consentita in java.


Fondamentalmente, il sistema di tipi di Java presume che ogni istanza di oggetto abbia un tipo, il cast di un oggetto a un supertipo funzionerà sempre e preserverà i riferimenti, e il cast di un riferimento a un sottotipo preserverà i riferimenti se l'istanza è di quel sottotipo o un sottotipo dello stesso. Tali presupposti sono utili e non credo sia possibile consentire l'ereditarietà multipla generalizzata in modo coerente con essi.
supercat

1

L'ereditarietà multipla non è consentita direttamente in Java, ma è consentita tramite le interfacce.

Motivo :

Ereditarietà multipla: introduce maggiore complessità e ambiguità.

Interfacce: interfacce sono classi completamente astratte in Java che forniscono un modo uniforme per delineare correttamente la struttura o il funzionamento interno del programma dalla sua interfaccia pubblicamente disponibile, con la conseguenza di una maggiore flessibilità e codice riutilizzabile, nonché maggiore controllo su come crei e interagisci con altre classi.

Più precisamente, sono un costrutto speciale in Java con la caratteristica aggiuntiva che consente di eseguire una sorta di ereditarietà multipla, ovvero classi che possono essere trasmesse a più di una classe.

Facciamo un semplice esempio.

  1. Supponiamo che ci siano 2 superclassi classi A e B con gli stessi nomi di metodo ma funzionalità differenti. Attraverso il codice seguente con la parola chiave (extends) non è possibile l'ereditarietà multipla.

       public class A                               
         {
           void display()
             {
               System.out.println("Hello 'A' ");
             }
         }
    
       public class B                               
          {
            void display()
              {
                System.out.println("Hello 'B' ");
              }
          }
    
      public class C extends A, B    // which is not possible in java
        {
          public static void main(String args[])
            {
              C object = new C();
              object.display();  // Here there is confusion,which display() to call, method from A class or B class
            }
        }
  2. Ma attraverso le interfacce, con la parola chiave (implements) è possibile l'ereditarietà multipla.

    interface A
        {
           // display()
        }
    
    
     interface B
        {
          //display()
        }
    
     class C implements A,B
        {
           //main()
           C object = new C();
           (A)object.display();     // call A's display
    
           (B)object.display(); //call B's display
        }
    }

0

Qualcuno può dirmi esattamente perché non è consentito?

Puoi trovare la risposta da questo link alla documentazione

Uno dei motivi per cui il linguaggio di programmazione Java non consente di estendere più di una classe è evitare i problemi di ereditarietà multipla dello stato, che è la capacità di ereditare i campi da più classi

Se è consentita l'ereditarietà multipla e quando crei un oggetto istanziando quella classe, quell'oggetto erediterà i campi da tutte le super classi della classe. Causerà due problemi.

  1. E se metodi o costruttori di diverse super classi istanziassero lo stesso campo?

  2. Quale metodo o costruttore avrà la precedenza?

Anche se ora è consentita l'ereditarietà multipla dello stato, è comunque possibile implementarla

Ereditarietà multipla di tipo : capacità di una classe di implementare più di un'interfaccia.

Ereditarietà multipla dell'implementazione (tramite metodi predefiniti nelle interfacce): capacità di ereditare definizioni di metodi da più classi

Fare riferimento a questa domanda SE correlata per ulteriori informazioni:

Ambiguità di ereditarietà multipla con interfaccia


0

In C ++ una classe può ereditare (direttamente o indirettamente) da più di una classe, che viene definita ereditarietà multipla .

C # e Java, tuttavia, limitano le classi a una singola ereditarietà, ciascuna classe eredita da una singola classe padre.

L'ereditarietà multipla è un modo utile per creare classi che combinano aspetti di due gerarchie di classi disparate, cosa che spesso accade quando si utilizzano framework di classi differenti all'interno di una singola applicazione.

Se due framework definiscono le proprie classi di base per le eccezioni, ad esempio, è possibile utilizzare più ereditarietà per creare classi di eccezioni che possono essere utilizzate con entrambi i framework.

Il problema con l'ereditarietà multipla è che può portare a ambiguità. L'esempio classico è quando una classe eredita da altre due classi, ognuna delle quali eredita dalla stessa classe:

class A {
    protected:
    bool flag;
};
class B : public A {};
class C : public A {};
class D : public B, public C {
    public:
    void setFlag( bool nflag ){
        flag = nflag; // ambiguous
    }
};

In questo esempio, il flagmembro dati è definito da class A. Ma class Ddiscende da class B e class C, da cui derivano entrambi A, quindi in sostanza sono disponibili due copie di flagperché due istanze di si Atrovano nella Dgerarchia di classi di. Quale vuoi impostare? Il compilatore si lamenterà del fatto che il riferimento a flagin Dè ambiguo . Una soluzione consiste nel disambiguare esplicitamente il riferimento:

B::flag = nflag;

Un'altra soluzione è dichiarare B e C come virtual base classes , il che significa che solo una copia di A può esistere nella gerarchia, eliminando ogni ambiguità.

Esistono altre complessità con l'ereditarietà multipla, come l'ordine in cui le classi base vengono inizializzate quando viene costruito un oggetto derivato o il modo in cui i membri possono essere nascosti inavvertitamente dalle classi derivate. Per evitare queste complessità, alcuni linguaggi si limitano al più semplice modello di ereditarietà singola.

Sebbene ciò semplifichi notevolmente l'ereditarietà, ne limita anche l'utilità perché solo le classi con un antenato comune possono condividere comportamenti. Le interfacce mitigano in qualche modo questa restrizione consentendo alle classi in gerarchie diverse di esporre interfacce comuni anche se non sono implementate condividendo codice.


-1

Immagina questo esempio: ho una classe Shape1

Ha CalcualteAreametodo:

Class Shape1
{

 public void CalculateArea()

     {
       //
     }
}

C'è un'altra classe Shape2che ha lo stesso metodo

Class Shape2
{

 public void CalculateArea()

     {

     }
}

Ora ho una classe figlia Circle, che deriva sia da Shape1 che da Shape2;

public class Circle: Shape1, Shape2
{
}

Ora, quando creo un oggetto per Circle e chiamo il metodo, il sistema non sa quale metodo di calcolo dell'area chiamare. Entrambi hanno le stesse firme. Quindi il compilatore si confonderà. Ecco perché non sono consentite più eredità.

Ma possono esserci più interfacce perché le interfacce non hanno la definizione del metodo. Anche entrambe le interfacce hanno lo stesso metodo, entrambe non hanno alcuna implementazione e verrà eseguito sempre il metodo nella classe figlia.


il designer del linguaggio può dare una chiamata al metodo esplicita come fanno in Interface. Non esiste un punto del genere! Un altro motivo è che ne dici di una classe astratta con metodi astratti ora come lo consideri?
Nomi Ali
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.