Esiste una ragione "reale" per odiare l'eredità multipla?

120

Mi è sempre piaciuta l'idea di avere un'ereditarietà multipla supportata in una lingua. Molto spesso però è intenzionalmente dimenticato e la presunta "sostituzione" è un'interfaccia. Le interfacce semplicemente non coprono tutte le stesse caratteristiche dell'eredità multipla a terra e questa restrizione può occasionalmente portare a un codice più completo.

L'unico motivo fondamentale che ho mai sentito per questo è il problema dei diamanti con le classi base. Non posso accettarlo. Per me, si tratta di un sacco di cose del tipo: "Beh, è possibile per rovinarlo, quindi è automaticamente una cattiva idea." Però puoi rovinare tutto in un linguaggio di programmazione, e intendo qualsiasi cosa. Non riesco a prenderlo sul serio, almeno non senza una spiegazione più approfondita.

Il solo fatto di essere a conoscenza di questo problema è il 90% della battaglia. Inoltre, penso di aver sentito qualcosa di anni fa in merito a un lavoro generale che comportava un algoritmo di "busta" o qualcosa del genere (questo suona un campanello, chiunque?).

Riguardo al problema dei diamanti, l'unico problema potenzialmente reale che riesco a pensare è se stai cercando di usare una libreria di terze parti e non riesci a vedere che due classi apparentemente non correlate in quella libreria hanno una classe base comune, ma oltre alla documentazione, una semplice funzione linguistica potrebbe, diciamo, richiedere che tu dichiari specificatamente il tuo intento di creare un diamante prima che ne venga compilato uno per te. Con una tale caratteristica, qualsiasi creazione di un diamante è intenzionale, spericolata o perché non si è a conoscenza di questa trappola.

In modo che tutto sia detto ... C'è qualche motivo reale che la maggior parte delle persone odia l'eredità multipla, o è tutto solo un po 'di isteria che causa più danni che benefici? C'è qualcosa che non vedo qui? Grazie.

Esempio

Car estende WheeledVehicle, KIASpectra estende Car ed Electronic, KIASpectra contiene Radio. Perché KIASpectra non contiene Electronic?

  1. Perché è un dispositivo elettronico. L'ereditarietà rispetto alla composizione dovrebbe sempre essere una relazione is-a vs. una relazione has-a.

  2. Perché è un dispositivo elettronico. Ci sono fili, circuiti stampati, interruttori, ecc. Tutto su e giù per quella cosa.

  3. Perché è un dispositivo elettronico. Se la tua batteria si spegne in inverno, ti trovi nei guai come se tutte le tue ruote fossero improvvisamente scomparse.

Perché non usare le interfacce? Prendi # 3, per esempio. Non voglio scriverlo più e più volte, e io veramente non voglio creare una classe di helper proxy bizzarra per fare questo:

private void runOrDont()
{
    if (this.battery)
    {
        if (this.battery.working && this.switchedOn)
        {
            this.run();
            return;
        }
    }
    this.dontRun();
}

(Non stiamo valutando se l'implementazione sia buona o cattiva.) Puoi immaginare come possano esserci diverse di queste funzioni associate a Electronic che non sono correlate a nulla in WheeledVehicle, e viceversa.

Non ero sicuro se accontentarmi di quell'esempio o meno, dato che lì c'è spazio per l'interpretazione. Potresti anche pensare in termini di Plane extending Vehicle e FlyingObject e Bird extending Animal e FlyingObject, o in termini di un esempio molto più puro.

    
posta Panzercrisis 14.11.2013 - 16:59
fonte

9 risposte

68

In molti casi, le persone usano l'ereditarietà per fornire un tratto a una classe. Ad esempio, pensa a un Pegaso. Con un'eredità multipla potresti essere tentato di dire che il Pegasus estende Cavallo e Uccello perché hai classificato l'Uccello come un animale con le ali.

Tuttavia, gli uccelli hanno altri tratti che Pegasi non ha. Ad esempio, gli uccelli depongono le uova, Pegasi ha una nascita viva. Se l'ereditarietà è il tuo unico mezzo per passare i tratti di condivisione, allora non c'è modo di escludere il tratto di deposizione delle uova dal Pegaso.

Alcune lingue hanno scelto di rendere i tratti un costrutto esplicito all'interno della lingua. Gli altri ti guidano delicatamente in quella direzione rimuovendo l'MI dalla lingua. Ad ogni modo, non riesco a pensare a un singolo caso in cui pensassi "Uomo, ho davvero bisogno che l'MI lo faccia correttamente".

Parliamo anche di quale eredità è VERAMENTE. Quando si eredita da una classe, si prende una dipendenza da quella classe, ma si deve anche supportare i contratti supportati dalla classe, sia impliciti che espliciti.

Prendi il classico esempio di un quadrato che eredita da un rettangolo. Il rettangolo espone una proprietà di lunghezza e larghezza e anche un metodo getPerimeter e getArea. Il quadrato sovrascrive la lunghezza e la larghezza in modo che quando uno è impostato l'altro sia impostato in modo che corrisponda a getPerimeter e getArea lavori allo stesso modo (2 * lunghezza + 2 * larghezza per perimetro e lunghezza * larghezza per area).

C'è un singolo caso di test che si interrompe se si sostituisce questa implementazione di un quadrato per un rettangolo.

var rectangle = new Square();
rectangle.length= 5;
rectangle.width= 6;
Assert.AreEqual(30, rectangle.GetArea()); 
//Square returns 36 because setting the width clobbers the length

È abbastanza difficile far funzionare le cose con una singola catena ereditaria. Diventa ancora peggio quando ne aggiungi un altro al mix.

Le insidie che ho citato con Pegasus in MI e le relazioni Rectangle / Square sono entrambi i risultati di un design inesperto per le classi. In sostanza, evitare l'ereditarietà multipla è un modo per aiutare gli sviluppatori iniziali a evitare di spararsi ai piedi. Come tutti i principi del design, la disciplina e l'allenamento basati su di essi ti permettono di scoprire in tempo quando è giusto separarsi da essi. Vedi il Dreyfus Model of Skill Acquisition , a livello di esperti, la tua conoscenza intrinseca trascende il rispetto delle massime / principi. Puoi "sentire" quando una regola non si applica.

E sono d'accordo sul fatto che ho in qualche modo imbrogliato con un esempio di "mondo reale" sul perché MI sia disapprovato.

Diamo un'occhiata a una struttura dell'interfaccia utente. Specificamente diamo un'occhiata ad alcuni widget che a prima vista potrebbero sembrare semplicemente una combinazione di altri due. Come un ComboBox. Un ComboBox è un TextBox con DropDownList di supporto. Cioè Posso digitare un valore o posso selezionare da un elenco di valori preordinati. Un approccio ingenuo sarebbe ereditare il ComboBox da TextBox e DropDownList.

Ma la tua casella di testo ricava il suo valore da ciò che l'utente ha digitato. Mentre il DDL ottiene il suo valore da ciò che l'utente seleziona. Chi ha precedenti? Il DDL potrebbe essere stato progettato per verificare e rifiutare qualsiasi input che non era incluso nell'elenco di valori originale. Superiamo questa logica? Ciò significa che dobbiamo esporre la logica interna affinché gli eredi ignorino. O peggio, aggiungi la logica alla classe base che è presente solo per supportare una sottoclasse (violando il Principio di inversione delle dipendenze ) .

Evitare l'MI ti aiuta a superare completamente questa trappola. E potrebbe portarti a estrarre tratti comuni e riutilizzabili dei tuoi widget UI in modo che possano essere applicati secondo necessità. Un eccellente esempio di ciò è la Proprietà associata a WPF che consente a elemento framework in WPF per fornire una proprietà che un altro elemento framework può utilizzare senza ereditare dall'elemento framework padre.

Ad esempio una griglia è un pannello di layout in WPF e ha proprietà collegate a colonne e righe che specificano dove posizionare un elemento figlio nella disposizione della griglia. Senza proprietà associate, se voglio disporre un pulsante all'interno di una griglia, il pulsante dovrebbe derivare dalla griglia in modo da poter accedere alle proprietà di colonne e righe.

Gli sviluppatori hanno ulteriormente approfondito questo concetto e utilizzato le proprietà associate come un modo per comporre il comportamento (ad esempio, ecco il mio post su come fare un ordinabile GridView utilizzando proprietà associate scritte prima che WPF includesse un DataGrid). L'approccio è stato riconosciuto come XAML Design Pattern chiamato Comportamenti collegati .

Si spera che questo abbia fornito un po 'più di intuizione sul perché l'Eredità Multipla è tipicamente disapprovata.

    
risposta data 14.11.2013 - 17:41
fonte
60

Is there something that I am not seeing here?

Consentire l'ereditarietà multipla rende le regole sugli overload delle funzioni e sulla distribuzione virtuale decisamente più complicate, così come l'implementazione della lingua attorno ai layout degli oggetti. Questi impattano un po 'i progettisti / implementatori linguistici e aumentano la barra già alta per ottenere un linguaggio finito, stabile e adottato.

Un altro argomento comune che ho visto (e realizzato a volte) è che avendo due + classi di base, il tuo oggetto viola quasi invariabilmente il Principio di Responsabilità Unica. Entrambe le due + classi di base sono belle classi autonome con la propria responsabilità (che causa la violazione) o sono tipi parziali / astratti che lavorano l'uno con l'altro per creare un'unica responsabilità coesiva.

In questo altro caso, hai 3 scenari:

  1. A non sa nulla di B - Ottimo, potresti combinare le classi perché sei stato fortunato.
  2. A sa di B - Perché A non ha ereditato da B?
  3. A e B si conoscono l'un l'altro - Perché non hai appena fatto una lezione? Che beneficio deriva dal rendere queste cose così accoppiate ma parzialmente sostituibili?

Personalmente, penso che l'ereditarietà multipla abbia un brutto colpo di rap, e che un sistema ben fatto di composizione stile tratto sarebbe davvero potente / utile ... ma ci sono molti modi in cui può essere implementato male e molto di ragioni non è una buona idea in una lingua come il C ++.

[modifica] riguardo al tuo esempio, è assurdo. Un Kia ha elettronica. ha un motore. Allo stesso modo, è l'elettronica avere una fonte di energia, che è semplicemente una batteria per auto. L'ereditarietà, per non parlare dell'ereditarietà multipla, non ha posto lì.

    
risposta data 14.11.2013 - 17:25
fonte
29

L'unica ragione per cui non è consentita è perché rende facile per le persone spararsi ai piedi.

Ciò che di solito segue in questo tipo di discussione è l'argomento se la flessibilità di avere gli strumenti è più importante della sicurezza di non sparare il piede. Non c'è una risposta decisamente corretta a questo argomento, perché come la maggior parte delle altre cose in programmazione, la risposta dipende dal contesto.

Se i tuoi sviluppatori sono a loro agio con MI, e MI ha senso nel contesto di ciò che stai facendo, allora ti mancherà moltissimo in un linguaggio che non lo supporta. Allo stesso tempo, se il team non si sente a proprio agio con esso, o non ne ha realmente bisogno e la gente lo usa "solo perché può", allora è controproducente.

Ma no, non esiste un argomento assolutamente convincente e assolutamente convincente che dimostri che l'ereditarietà multipla è una cattiva idea.

Modifica

Le risposte a questa domanda sembrano essere unanime. Per il bene di essere l'avvocato del diavolo fornirò un buon esempio di ereditarietà multipla, dove non farlo porta a hack.

Supponiamo che stiate progettando un'applicazione per i mercati dei capitali. È necessario un modello di dati per i titoli. Alcuni titoli sono prodotti azionari (azioni, fondi comuni di investimento immobiliare, ecc.) Altri sono titoli di debito (obbligazioni, obbligazioni societarie), altri sono derivati (opzioni, futures). Quindi, se stai evitando l'MI, realizzerai un albero ereditario molto chiaro e semplice. Un titolo erediterà il patrimonio, Bond erediterà il debito. Ottimo finora, ma per quanto riguarda i derivati? Possono essere basati su prodotti di tipo Equity o prodotti simili a Debit? Ok, suppongo che faremo del nostro ramo di albero ereditario di più. Tieni presente che alcuni derivati sono basati su prodotti azionari, prodotti di debito o nessuno dei due. Quindi il nostro albero ereditario si sta complicando. Poi arriva l'analista di business e ti dice che ora supportiamo i titoli indicizzati (opzioni di indice, opzioni future dell'indice). E queste cose possono essere basate su Equity, Debt o Derivative. Questo sta diventando disordinato! L'opzione futura del mio indice deriva Equity- > Stock- > Option- > Index? Perché non scegliere Equity- > Stock- > Index- > Option? Che cosa succede se un giorno trovo entrambi nel mio codice (Questo è successo, la storia vera)?

Il problema qui è che questi tipi fondamentali possono essere mescolati in qualsiasi permutazione che non deriva naturalmente l'una dall'altra. Gli oggetti sono definiti da una relazione è , quindi la composizione non ha alcun senso. L'ereditarietà multipla (o il concetto simile di mixin) è l'unica rappresentazione logica qui.

La vera soluzione a questo problema è di avere i tipi di Equity, Debt, Derivative, Index definiti e mixati usando l'ereditarietà multipla per creare il tuo modello di dati. Ciò creerà oggetti che entrambi hanno senso e prestano facilmente per riutilizzare il codice.

    
risposta data 14.11.2013 - 20:03
fonte
11

Le altre risposte qui sembrano essere per lo più in teoria. Quindi, ecco un concreto esempio di Python, semplificato in basso, in cui mi sono davvero imbattuto a fondo, il che ha richiesto una discreta quantità di refactoring:

class Foo(object):
   def zeta(self):
      print "foozeta"

class Bar(object):
   def zeta(self):
      print "barzeta"

   def barstuff(self):
      print "barstuff"
      self.zeta()

class Bang(Foo, Bar):
   def stuff(self):
      self.zeta()
      print "---"
      self.barstuff()

z = Bang()
z.stuff()

Bar è stato scritto assumendo che avesse una propria implementazione di zeta() , che in genere è una buona ipotesi. Una sottoclasse dovrebbe sovrascriverla in modo appropriato in modo che faccia la cosa giusta. Sfortunatamente, i nomi sono coincidenti allo stesso modo - hanno fatto cose piuttosto diverse, ma Bar stava chiamando l'implementazione di Foo :

foozeta
---
barstuff
foozeta

È piuttosto frustrante quando non ci sono errori lanciati, l'applicazione inizia a comportarsi solo in modo leggermente sbagliato, e il cambio di codice che lo ha causato (creando Bar.zeta ) non sembra essere dove si trova il problema.

    
risposta data 14.11.2013 - 21:00
fonte
8

Direi che non ci sono problemi reali con MI nella lingua giusta . La chiave è di consentire le strutture di diamante, ma richiedono che i sottotipi forniscano il proprio override, invece del compilatore che seleziona una delle implementazioni basate su alcune regole.

Lo faccio in Guava , una lingua su cui sto lavorando. Una caratteristica di Guava è che possiamo invocare l'implementazione di un metodo di un supertipo specifico. Quindi è facile indicare quale implementazione di supertipo dovrebbe essere "ereditata", senza alcuna sintassi speciale:

type Sequence[+A] {
  String toString() {
    return "[" + ... + "]";
  }
}

type Set[+A] {
  String toString() {
    return "{" + ... + "}";
  }
}

type OrderedSet[+A] extends Sequence[A], Set[A] {
  String toString() {
    // This is Guava's syntax for statically invoking instance methods
    return Set.toString(this);
  }
}

Se non avessimo dato a OrderedSet il proprio toString , avremmo ottenuto un errore di compilazione. Nessuna sorpresa.

Trovo che MI sia particolarmente utile con le raccolte. Ad esempio, mi piace usare un tipo RandomlyEnumerableSequence per evitare di dichiarare getEnumerator per array, deques e così via:

type Enumerable[+A] {
  Source[A] getEnumerator();
}

type Sequence[+A] extends Enumerable[A] {
  A get(Int index);
}

type RandomlyEnumerableSequence[+A] extends Sequence[A] {
  Source[A] getEnumerator() {
    ...
  }
}

type DynamicArray[A] extends MutableStack[A],
                             RandomlyEnumerableSequence[A] {
  // No need to define getEnumerator.
}

Se non avessimo l'MI, potremmo scrivere un RandomAccessEnumerator per più raccolte da usare, ma dover scrivere un breve metodo getEnumerator aggiunge ancora il boilerplate.

Allo stesso modo, MI è utile per ereditare implementazioni standard di equals , hashCode e toString per le raccolte.

    
risposta data 15.11.2013 - 06:45
fonte
7

L'ereditarietà, più o meno, non è così importante. Se due oggetti di diverso tipo sono sostituibili, questo è ciò che conta, anche se non sono collegati per ereditarietà.

Un elenco collegato e una stringa di caratteri hanno poco in comune e non devono necessariamente essere collegati per ereditarietà, ma è utile se posso usare una funzione length per ottenere il numero di elementi in uno dei due.

L'ereditarietà è un trucco per evitare l'implementazione ripetuta del codice. Se l'ereditarietà salva il tuo lavoro e l'ereditarietà multipla ti risparmia ancora più lavoro rispetto all'ereditarietà singola, allora questa è la giustificazione necessaria.

Ho il sospetto che alcune lingue non implementino molto bene l'eredità multipla, e per i professionisti di quelle lingue, questo è ciò che significa l'ereditarietà multipla. Menziona l'ereditarietà multipla con un programmatore C ++ e ciò che viene in mente è qualcosa sui problemi quando una classe finisce con due copie di una base tramite due diversi percorsi di ereditarietà e se usare virtual su una classe base e confusione su come i distruttori sono chiamati e così via.

In molte lingue, l'ereditarietà della classe è confusa con l'ereditarietà dei simboli. Quando si ricava una classe D da una classe B, non solo si crea una relazione di tipo, ma poiché queste classi fungono anche da spazi dei nomi lessicali, si ha a che fare con l'importazione di simboli dallo spazio dei nomi B allo spazio dei nomi D, oltre a la semantica di ciò che sta accadendo con i tipi B e D stessi. L'ereditarietà multipla comporta quindi problemi di conflitto tra simboli. Se ereditiamo da card_deck e graphic , entrambi "hanno" un metodo draw , cosa significa draw dell'oggetto risultante? Un sistema di oggetti che non ha questo problema è quello in Common Lisp. Forse non per coincidenza, l'ereditarietà multipla viene usata nei programmi Lisp.

Implementato male, qualsiasi cosa scomoda (come l'ereditarietà multipla) dovrebbe essere odiato.

    
risposta data 14.11.2013 - 20:39
fonte
1

Per quanto ne so, parte del problema (oltre a rendere il tuo disegno un po 'più difficile da capire (ma comunque più facile da codificare)) è che il compilatore sta per risparmiare abbastanza spazio per i tuoi dati di classe, permettendo così a quantità enorme di spreco di memoria nel seguente caso:

(Il mio esempio potrebbe non essere il migliore, ma cerca di ottenere l'essenza di più spazi di memoria per lo stesso scopo, è stata la prima cosa che mi è venuta in mente: P)

Considerando un DDD in cui il cane di classe si estende da canino e da animale domestico, un canino ha una variabile che indica la quantità di cibo che deve mangiare (un numero intero) sotto il nome dietKg, ma un animale domestico ha anche un'altra variabile a tale scopo di solito sotto lo stesso nome (a meno che tu non imposti un altro nome di variabile, allora avrai codificato codice extra, che era il problema iniziale che avresti voluto evitare, per gestire e mantenere l'integrità delle variabili di bouth), allora avrai due spazi di memoria per lo stesso identico scopo, per evitare ciò dovrai modificare il tuo compilatore per riconoscere quel nome sotto lo stesso spazio dei nomi e assegnare un singolo spazio di memoria a quei dati, che sfortunatamente è possibile determinare in tempo di compilazione.

Potresti, ovviamente, progettare un languaje per specificare che tale variabile potrebbe avere già uno spazio definito da qualche altra parte, ma alla fine il programmatore dovrebbe specificare dov'è lo spazio di memoria a cui questa variabile fa riferimento (e ancora un altro codice ).

Credimi, le persone che implementano questo pensiero sono davvero dure per tutto questo, ma sono felice che tu abbia chiesto, la tua gentile prospettiva è quella che cambia i paradigmi;) e, concordo, non sto dicendo che sia impossibile (ma molte ipotesi e un compilatore a più fasi devono essere implementate, e una molto complessa), sto solo dicendo che non esiste ancora, se si avvia un progetto per il proprio compilatore in grado di fare "questo" (ereditarietà multipla) per favore lasciatemi so, sarò lieto di unirmi alla tua squadra.

    
risposta data 14.11.2013 - 17:52
fonte
-1

Da un po 'di tempo non mi è mai venuto in mente quanto siano totalmente diversi i compiti di programmazione degli altri - e quanto sia utile se i linguaggi e i pattern utilizzati sono adattati allo spazio del problema.

Quando lavori da solo o per lo più isolato sul codice che hai scritto, è uno spazio completamente diverso da quello di ereditare una base di codice da 40 persone in India che ci hanno lavorato per un anno prima di consegnarlo a te senza alcun aiuto di transizione.

Immagina di essere stato appena assunto dalla compagnia dei tuoi sogni e poi di aver ereditato una tale base di codice. Immagina inoltre che i consulenti stiano imparando (e quindi infatuati) sull'eredità e sull'eredità multipla ... Potresti immaginare su cosa potresti lavorare.

Quando si eredita il codice, la caratteristica più importante è che è comprensibile e che i pezzi sono isolati in modo che possano essere elaborati indipendentemente. Certo, quando stai scrivendo per la prima volta le strutture di codice come l'ereditarietà multipla potrebbe salvarti un po 'di duplicazione e sembra che si adatti al tuo stato d'animo logico in quel momento, ma il prossimo ha solo più cose da sbrogliare.

Ogni interconnessione nel tuo codice rende anche più difficile capire e modificare i pezzi in modo indipendente, doppiamente con un'eredità multipla.

Quando lavori come parte di un team, vuoi indirizzare il codice più semplice possibile che non ti dia assolutamente nessuna logica ridondante (questo è ciò che significa davvero ASCIUGA, non che non dovresti digitare molto solo che non devi mai cambiare il tuo codice in 2 punti per risolvere un problema!)

Esistono modi più semplici per ottenere il codice DRY rispetto all'Eredità Multipla, quindi includerlo in una lingua può solo aprirti ai problemi inseriti da altri che potrebbero non essere allo stesso livello di comprensione di te. È persino allettante se la tua lingua non è in grado di offrirti un modo semplice / meno complesso per mantenere il tuo codice ASCIUTTO.

    
risposta data 16.11.2013 - 05:22
fonte
-3

Il più grande argomento contro l'ereditarietà multipla è che alcune abilità utili possono essere fornite, e alcuni utili assiomi possono contenere, in un quadro che la limita strongmente (*), ma non può non essere fornita e / o trattenere senza tali restrizioni. Tra questi:

  • La possibilità di avere più moduli compilati separatamente include classi che ereditano da altre classi del modulo e ricompilare un modulo contenente una classe base senza dover ricompilare ogni modulo che eredita da quella classe base.

  • La possibilità per un tipo di ereditare un'implementazione di un membro da una classe genitore senza che il tipo derivato debba riorganizzarlo

  • L'assioma che qualsiasi istanza di oggetto può essere upcast o downcast direttamente a se stessa o ai suoi tipi di base, e tali upcast e downcast, in qualsiasi combinazione o sequenza, sono sempre di conservazione dell'identità

  • L'assioma che se una classe derivata esegue l'override e le catene su un membro della classe base, il membro di base verrà richiamato direttamente dal codice di concatenazione e non verrà invocato da nessun'altra parte.

(*) In genere, richiedendo che i tipi che supportano l'ereditarietà multipla vengano dichiarati come "interfacce" anziché come classi, e non consentendo alle interfacce di fare tutto ciò che possono fare le normali classi.

Se si desidera consentire l'ereditarietà multipla generalizzata, qualcos'altro deve cedere. Se X e Y ereditano entrambi da B, entrambi sovrascrivono lo stesso membro M e si incatenano all'implementazione di base, e se D eredita da X e Y ma non esegue l'override di M, allora viene data un'istanza q di tipo D, cosa dovrebbe (( B) q) .M () fare? Disabilitare un cast di questo tipo violerebbe l'assioma che dice che qualsiasi oggetto può essere trasmesso a qualsiasi tipo di base, ma qualsiasi comportamento possibile del cast e della chiamata del membro violerebbe l'assioma riguardante il concatenamento del metodo. Si potrebbe richiedere che le classi vengano caricate solo in combinazione con la versione specifica di una classe base a cui sono state compilate, ma spesso è scomoda. Avere il runtime di rifiutare di caricare qualsiasi tipo in cui qualsiasi antenato può essere raggiunto da più di una rotta potrebbe essere praticabile, ma limiterebbe notevolmente l'utilità dell'ereditarietà multipla. Permettendo percorsi di ereditarietà condivisi solo quando non esistono conflitti creerebbe situazioni in cui una vecchia versione di X sarebbe compatibile con una Y vecchia o nuova, e una vecchia Y sarebbe compatibile con una X vecchia o nuova, ma la nuova X e la nuova Y sarebbero essere compatibile, anche se nessuno dei due ha fatto qualcosa che di per sé dovrebbe essere considerato un cambiamento irrisorio.

Alcuni linguaggi e framework consentono l'ereditarietà multipla, sulla base della teoria che ciò che è guadagnato da MI è più importante di quello che deve essere abbandonato per consentirlo. I costi del MI sono significativi, tuttavia, e per molti casi le interfacce forniscono il 90% dei vantaggi dell'MI a una piccola frazione del costo.

    
risposta data 14.11.2013 - 23:14
fonte