Un uso per ereditarietà multipla?

Qualcuno può pensare a qualsiasi situazione per utilizzare l’ereditarietà multipla? Ogni caso che posso pensare può essere risolto dall’operatore del metodo

AnotherClass() { return this->something.anotherClass; } 

La maggior parte degli usi di full scale Multiple inheritance sono per i mixin. Come esempio:

 class DraggableWindow : Window, Draggable { } class SkinnableWindow : Window, Skinnable { } class DraggableSkinnableWindow : Window, Draggable, Skinnable { } 

eccetera…

Nella maggior parte dei casi, è meglio utilizzare l’ereditarietà multipla per eseguire l’ereditarietà dell’interfaccia.

 class DraggableWindow : Window, IDraggable { } 

Quindi implementa l’interfaccia IDraggable nella tua class DraggableWindow. È troppo difficile scrivere buoni corsi di mixaggio.

Il vantaggio dell’approccio MI (anche se si utilizza solo MI interfaccia) è che è ansible trattare tutti i tipi di Windows diversi come oggetti Window, ma avere la flessibilità necessaria per creare cose che non sarebbero possibili (o più difficili) con singoli eredità.

Ad esempio, in molti framework di class si vede qualcosa di simile a questo:

 class Control { } class Window : Control { } class Textbox : Control { } 

Ora, supponi di volere una caratteristica Textbox with Window? Come essere trascinabili, con una barra del titolo, ecc … Potresti fare qualcosa del genere:

 class WindowedTextbox : Control, IWindow, ITexbox { } 

Nel modello di ereditarietà singola, non è ansible ereditare facilmente da Windows e da Textbox senza avere problemi con oggetti di controllo duplicati e altri tipi di problemi. Puoi anche trattare un WindowedTextbox come una finestra, una casella di testo o un controllo.

Inoltre, per risolvere il tuo idioma .anotherClass (), .anotherClass () restituisce un object diverso, mentre l’ereditarietà multipla consente di utilizzare lo stesso object per scopi diversi.

Trovo che l’ereditarietà multipla sia particolarmente utile quando si usano classi di mixin .

Come affermato in Wikipedia:

Nei linguaggi di programmazione orientati agli oggetti, un mixin è una class che fornisce una determinata funzionalità che deve essere ereditata da una sottoclass, ma non è pensata per stare da sola.

Un esempio di come il nostro prodotto utilizza le classi di mixin è per scopi di salvataggio e ripristino della configurazione. Esiste una class di mixin astratta che definisce un insieme di metodi virtuali puri. Qualsiasi class che sia salvabile eredita dalla class mixin di salvataggio / ripristino che fornisce automaticamente la funzionalità di salvataggio / ripristino appropriata.

Ma possono anche ereditare da altre classi come parte della loro normale struttura di class, quindi è abbastanza comune per queste classi utilizzare l’ereditarietà multipla a questo riguardo.

Un esempio di ereditarietà multipla:

 class Animal { virtual void KeepCool() const = 0; } class Vertebrate { virtual void BendSpine() { }; } class Dog : public Animal, public Vertebrate { void KeepCool() { Pant(); } } 

Ciò che è più importante quando si fa qualsiasi forma di eredità pubblica (singola o multipla) è rispettare la relazione. Una class dovrebbe ereditare solo da una o più classi se “è” uno di quegli oggetti. Se semplicemente “contiene” uno di questi oggetti, è preferibile utilizzare l’aggregazione o la composizione.

L’esempio sopra è ben strutturato perché un cane è un animale e anche un vertebrato.

La maggior parte delle persone usa l’ereditarietà multipla nel contesto dell’applicazione di più interfacce a una class. Questo è l’approccio Java e C #, tra gli altri, impone.

C ++ ti consente di applicare più classi base in modo abbastanza libero, in una relazione is-a tra i tipi. Quindi, puoi trattare un object derivato come una qualsiasi delle sue classi base.

Un altro uso, come sottolinea LeopardSkinPillBoxHat , è nei missaggi. Un eccellente esempio di ciò è la libreria Loki , dal libro Modern C ++ Design di Andrei Alexandrescu. Usa le sue classi di politica che specificano il comportamento oi requisiti di una determinata class attraverso l’ereditarietà.

Un altro uso è quello che semplifica un approccio modulare che consente l’ indipendenza API attraverso l’uso della delega di class sorella nella gerarchia dei diamanti temuta spesso.

Gli usi per MI sono molti. Il potenziale di abuso è ancora maggiore.

Java ha interfacce. Il C ++ no.

Pertanto, è ansible utilizzare l’ereditarietà multipla per emulare la funzionalità dell’interfaccia. Se sei un programmatore C # e Java, ogni volta che utilizzi una class che estende una class base ma implementa anche alcune interfacce, sei in grado di ammettere l’ereditarietà multipla in alcune situazioni.

Un caso ho lavorato su stampanti di etichette abilitate alla rete recentemente coinvolte. Abbiamo bisogno di stampare etichette, quindi abbiamo una LabelPrinter di class. Questa class ha chiamate virtuali per la stampa di diverse etichette. Ho anche una class generica per cose connesse a TCP / IP, che possono connettersi, inviare e ricevere. Quindi, quando ho avuto bisogno di implementare una stampante, ha ereditato sia dalla class LabelPrinter che dalla class TcpIpConnector.

Penso che l’esempio di fmsf sia una ctriggers idea. Una macchina non è un pneumatico o un motore. Dovresti usare la composizione per quello.

MI (di implementazione o interfaccia) può essere utilizzato per aggiungere funzionalità. Queste sono spesso chiamate classi di mixin. Immagina di avere una GUI. C’è una class di viste che gestisce il disegno e una class Drag & Drop che gestisce il trascinamento. Se hai un object che fa entrambi, avresti una class simile

 class DropTarget{ public void Drop(DropItem & itemBeingDropped); ... } class View{ public void Draw(); ... } /* View you can drop items on */ class DropView:View,DropTarget{ } 

Penso che sarebbe molto utile per il codice boilerplate. Ad esempio, il pattern IDisposable è esattamente lo stesso per tutte le classi in .NET. Quindi perché ri-digitare quel codice più e più volte?

Un altro esempio è ICollection. La stragrande maggioranza dei metodi di interfaccia sono implementati esattamente nello stesso modo. Ci sono solo un paio di metodi che sono in realtà unici per la tua class.

Sfortunatamente l’ereditarietà multipla è molto facile da abusare. Le persone inizieranno rapidamente a fare cose stupide come la class LabelPrinter eredita dalla loro class TcpIpConnector invece di limitarsi a contenerla.

È vero che la composizione di un’interfaccia (simile a Java o C #) più l’inoltro a un helper possono emulare molti degli usi comuni dell’ereditarietà multipla (in particolare i mixin). Tuttavia questo viene fatto a costo di quel codice di inoltro che viene ripetuto (e violando DRY).

MI apre una serie di aree difficili, e più recentemente alcuni progettisti di linguaggi hanno preso decisioni che le potenziali insidie ​​di MI superano i benefici.

Allo stesso modo si può discutere contro i generici (i contenitori eterogenei funzionano, i loop possono essere sostituiti con la ricorsione (coda) e quasi ogni altra caratteristica dei linguaggi di programmazione. Solo perché è ansible lavorare senza una funzione non significa che quella funzione sia priva di valore o non possa aiutare a esprimere efficacemente le soluzioni.

Una ricca diversità di lingue e famiglie linguistiche rende più facile per noi sviluppatori scegliere strumenti validi che risolvono il problema aziendale. La mia cassetta degli attrezzi contiene molti oggetti che uso raramente, ma in quelle occasioni non voglio trattare tutto come un chiodo.

Un esempio di come il nostro prodotto utilizza le classi di mixin è per scopi di salvataggio e ripristino della configurazione. Esiste una class di mixin astratta che definisce un insieme di metodi virtuali puri. Qualsiasi class che sia salvabile eredita dalla class mixin di salvataggio / ripristino che fornisce automaticamente la funzionalità di salvataggio / ripristino appropriata.

Questo esempio non illustra realmente l’utilità dell’ereditarietà multipla. Ciò che viene definito qui è un’interfaccia. L’ereditarietà multipla ti consente di ereditare anche il comportamento. Qual è il punto di mixin.

Un esempio; a causa della necessità di preservare la retrocompatibilità, devo implementare i miei metodi di serializzazione.

Quindi ogni object ha un metodo di lettura e memorizzazione come questo.

 Public Sub Store(ByVal File As IBinaryWriter) Public Sub Read(ByVal File As IBinaryReader) 

Voglio anche essere in grado di assegnare e clonare l’object. Quindi mi piacerebbe questo su ogni object.

 Public Sub Assign(ByVal tObject As ) Public Function Clone() As  

Ora in VB6 ho questo codice ripetuto più e più volte.

 Public Assign(ByVal tObject As ObjectClass) Me.State = tObject.State End Sub Public Function Clone() As ObjectClass Dim O As ObjectClass Set O = New ObjectClass O.State = Me.State Set Clone = 0 End Function Public Property Get State() As Variant StateManager.Clear Me.Store StateManager State = StateManager.Data End Property Public Property Let State(ByVal RHS As Variant) StateManager.Data = RHS Me.Read StateManager End Property 

Notare che Statemanager è un stream che legge e memorizza gli array di byte.

Questo codice viene ripetuto decine di volte.

Ora in .NET sono in grado di aggirare questo usando una combinazione di generici ed ereditarietà. Il mio object sotto la versione .NET ottiene Assegna, Clona e Stato quando ereditano da MyAppBaseObject. Ma non mi piace il fatto che ogni object erediti da MyAppBaseObject.

Preferisco semplicemente mixare l’interfaccia Assign Clone AND BEHAVIOR. Meglio ancora mescolare separatamente l’interfaccia Leggi e Memorizza quindi essere in grado di mixare in Assegna e Clona. Sarebbe un codice più pulito secondo me.

Ma i tempi in cui riuso il comportamento sono DWARF nel momento in cui utilizzo Interface. Questo perché l’objective della maggior parte delle gerarchie di oggetti NON riguarda il riutilizzo del comportamento, ma la definizione precisa della relazione tra diversi oggetti. Quali interfacce sono progettate per. Quindi, anche se sarebbe bello che C # (o VB.NET) avesse delle capacità per farlo, non è uno stopper secondo me.

L’intero motivo per cui questo è anche un problema che il C ++ ha azzuffato la palla all’inizio quando si trattava del problema dell’interfaccia vs ereditarietà. Quando ha debuttato OOP, tutti pensavano che il riutilizzo del comportamento fosse la priorità. Ma questo si è rivelato una chimera e utile solo per circostanze specifiche, come la creazione di un framework UI.

Successivamente è stata sviluppata l’idea di mixin (e altri concetti correlati nella programmazione orientata all’aspetto). L’ereditarietà multipla è stata trovata utile nella creazione di mix-in. Ma C # è stato sviluppato poco prima che questo fosse ampiamente riconosciuto. Probabilmente verrà sviluppata una syntax alternativa per farlo.

Ho il sospetto che in C ++, MI sia il miglior uso come parte di un framework (le classi di mix-in precedentemente discusse). L’unica cosa che so per certo è che ogni volta che ho provato ad usarlo nelle mie app, ho finito per rimpiangere la scelta, spesso strappandola e sostituendola con il codice generato.

MI è un altro di quelli che lo usano se ne hai VERAMENTE bisogno, ma assicurati di aver davvero bisogno di strumenti.

Il seguente esempio è per lo più qualcosa che vedo spesso in C ++: a volte può essere necessario a causa delle classi di utilità di cui hai bisogno ma a causa del loro design non può essere usato attraverso la composizione (almeno non in modo efficiente o senza rendere il codice ancora più confuso che ricadere su mult. ereditarietà). Un buon esempio è che si ha una class base astratta A e una class derivata B, e B deve anche essere una sorta di class serializzabile, quindi deve derivare, diciamo, da un’altra class astratta chiamata Serializable. È ansible evitare l’MI, ma se Serializable contiene solo alcuni metodi virtuali e ha bisogno di un accesso approfondito ai membri privati ​​di B, può valere la pena di confondere l’albero ereditario solo per evitare di fare dichiarazioni di amici e dare accesso agli interni di B ad alcuni class di composizione helper.

Dovevo usarlo oggi, in realtà …

Ecco la mia situazione: avevo un modello di dominio rappresentato in memoria dove un A conteneva zero o più Bs (rappresentati in un array), ogni B ha zero o più Cs e Cs a Ds. Non ho potuto cambiare il fatto che fossero array (la fonte di questi array proveniva dal codice generato automaticamente dal processo di compilazione). Ogni istanza doveva tenere traccia di quale indice nell’array principale a cui appartenevano. Dovevano anche tenere traccia dell’istanza del genitore (troppi dettagli sul perché). Ho scritto qualcosa del genere (c’era dell’altro, e questo non è sintatticamente corretto, è solo un esempio):

 class Parent { add(Child c) { children.add(c); c.index = children.Count-1; c.parent = this; } Collection children } class Child { Parent p; int index; } 

Quindi, per i tipi di dominio, ho fatto questo:

 class A : Parent class B : Parent, Child class C : Parent, Child class D : Child 

L’implementazione effettiva era in C # con interfacce e generici, e non potevo fare l’ereditarietà multipla come avrei fatto se la lingua l’avesse supportata (era necessario fare un po ‘di copiatura). Così, ho pensato di cercare SO per vedere cosa pensa la gente dell’eredità multipla, e ho ricevuto la tua domanda;)

Non ho potuto usare la tua soluzione di .anotherClass, a causa dell’implementazione di add per Parent (fa riferimento a questo – e volevo che non fosse un’altra class).

È peggiorato perché il codice generato aveva una sottoclass che non era né un genitore né un bambino … più copia incolla.