2010-02-10 9 views
23

Composizione ed eredità.C'è qualcosa che la composizione non può realizzare che l'eredità può?

Sono consapevole che sono entrambi strumenti da scegliere quando appropriato e il contesto è molto importante nella scelta tra composizione ed eredità. Tuttavia, la discussione sul contesto appropriato per ciascuno di solito è un po 'confusa; questo mi ha fatto cominciare a considerare quanto distintamente l'ereditarietà e il polimorfismo siano aspetti separati dell'OOP tradizionale.

Il polimorfismo consente di specificare le relazioni "è-a" in modo uguale e l'ereditarietà. In particolare, l'ereditarietà da una classe di base crea implicitamente una relazione polimorfica tra quella classe e le sue sottoclassi. Tuttavia, mentre il polimorfismo può essere implementato utilizzando interfacce pure, l'ereditarietà complica la relazione polimorfica trasferendo simultaneamente i dettagli di implementazione. In questo modo, l'ereditarietà è ben diversa dal puro polimorfismo.

Come strumento, eredità serve programmatori diverso rispetto polimorfismo (attraverso interfacce puri) semplificando attuazione riutilizzo in casi banali. Nella maggior parte dei casi, tuttavia, i dettagli di implementazione di una superclasse sono in conflitto con i requisiti di una sottoclasse. Questo è il motivo per cui abbiamo "overrides" e "member hiding". In questi casi, il riutilizzo dell'implementazione offerto dall'ereditarietà viene acquistato con l'ulteriore sforzo di verificare le modifiche dello stato e i percorsi di esecuzione tra i livelli a cascata del codice: i dettagli di implementazione "appiattiti" completi della sottoclasse sono distribuiti tra più classi, il che di solito significa più file, di cui si applicano solo le parti alla sottoclasse in questione. Guardare attraverso quella gerarchia è assolutamente necessario quando si ha a che fare con l'ereditarietà, perché senza guardare il codice della superclasse, non c'è modo di sapere quali dettagli non nascosti stanno monkeying con il tuo stato o deviando l'esecuzione.

In confronto, l'uso esclusivo della composizione garantisce che vedrete quale stato può essere modificato da oggetti esplicitamente istanziati i cui metodi sono invocati a vostra discrezione. L'implementazione appiattita non è ancora stata raggiunta (e in realtà non è nemmeno auspicabile, dal momento che il vantaggio della programmazione strutturata è l'incapsulamento e l'astrazione dei dettagli di implementazione) ma si ottiene comunque il riutilizzo del codice, e si dovrà solo guardare in un posto quando il codice si comporta male.

Con l'obiettivo di testare queste idee in pratica, evitando l'eredità tradizionale per una combinazione di pura polimorfismo dell'interfaccia-based e composizione di oggetti, mi chiedo,

v'è la composizione oggetto nulla e interfacce non possono realizzare questo ereditarietà può ?

Modifica

Nelle risposte finora, ewernli crede non ci sono prodezze tecniche disponibili per una tecnica, ma non l'altro; più avanti menziona come differenti modelli e approcci progettuali sono inerenti a ciascuna tecnica. Questo è ragionevole. Tuttavia, il suggerimento mi porta a perfezionare la mia domanda chiedendo se l'uso esclusivo della composizione e delle interfacce al posto dell'eredità tradizionale proibirebbe l'uso di modelli di progettazione importanti? E se sì, non ci sono modelli equivalenti da usare nella mia situazione?

+2

Personalmente, mi piacciono i mixin. :) –

+1

Non ho il tempo di verificare la duplicazione effettiva, ma questo tema di eredità v. Composizione è perennemente visitato su SO, ad es. http://stackoverflow.com/questions/216523/object-oriented-best-practices-inheritance-v-composition-v-interfaces o http://stackoverflow.com/questions/1598722/should-i-use-inheritance -o-composizione. "Si può sempre dare una svolta a questo tema e chiamarlo romanzo sulle cose ..." Tuttavia, una cosa che dovremmo accettare è che questo tipo di domanda non porta a risposte definitive o addirittura autorevoli. Forse un CW potrebbe essere il formato più appropriato ... – mjv

+0

Non intendevo riproporre un dibattito stanco. Certo, il modo in cui ho affermato che il mio caso era praticamente un campione unilaterale del suddetto dibattito, ma il mio interesse principale è nel rispondere se esiste o meno un uso per l'ereditarietà che non può essere realmente sostituito con la composizione e le interfacce. p.s., che cos'è un formato CW? Forse proverò che ... –

risposta

21

Tecnicamente tutto ciò che può essere realizzato con l'ereditarietà può essere realizzato anche con delega. Quindi la risposta sarebbe "no".

Trasformare eredità in delegazione

Diciamo abbiamo le seguenti classi implementate con l'ereditarietà:

public class A { 
    String a = "A"; 
    void doSomething() { .... } 
    void getDisplayName() { return a } 
    void printName { System.out.println(this.getDisplayName() }; 
} 

public class B extends A { 
    String b = "B"; 
    void getDisplayName() { return a + " " + b; } 
    void doSomething() { super.doSomething() ; ... }  
} 

La roba funziona bene, e chiamando printName su un'istanza di B stamperà "A B" nel console.

Ora, se riscriviamo che, con la delega, otteniamo:

public class A { 
    String a = "A"; 
    void doSomething() { .... } 
    void getDisplayName() { return a } 
    void printName { System.out.println(this.getName() }; 
} 

public class B { 
    String b = "B"; 
    A delegate = new A(); 
    void getDisplayName() { return delegate.a + " " + b; } 
    void doSomething() { delegate.doSomething() ; ... } 
    void printName() { delegate.printName() ; ... } 
} 

Dobbiamo definire printName in B e anche per creare il delegato quando B viene creata un'istanza. Una chiamata a doSomething funzionerà in modo simile all'ereditarietà. Ma una chiamata a printName stamperà "A" nella console. In effetti con la delega, abbiamo perso il potente concetto di "questo" legato all'istanza dell'oggetto e ai metodi di base che sono in grado di chiamare metodi che hanno la precedenza.

Questo può essere risolto nella lingua supporta la delega pura. Con la delega pura, "questo" nel delegato farà ancora riferimento all'istanza di B. Il che significa che this.getName() avvierà l'invio del metodo dalla classe B. Otteniamo lo stesso risultato dell'ereditarietà. Questo è il meccanismo utilizzato nella lingua prototype-based come Self per cui la delega dispone di una funzione incorporata (È possibile leggere here come funziona l'ereditarietà in Sé).

Ma Java non ha una delega pura. Quando sono bloccati? No davvero, possiamo ancora farlo noi stessi con qualche sforzo in più:

public class A implements AInterface { 
    String a = "A"; 
    AInterface owner; // replace "this" 
    A (AInterface o) { owner = o } 
    void doSomething() { .... } 
    void getDisplayName() { return a } 
    void printName { System.out.println(owner.getName() }; 
} 

public class B implements AInterface { 
    String b = "B"; 
    A delegate = new A(this); 
    void getDisplayName() { return delegate.a + " " + b; } 
    void doSomething() { delegate.doSomething() ; ... } 
    void printName() { delegate.printName() ; ... } 
} 

Ci sono fondamentalmente ri-attuazione quanto l'eredità incorporato fornisce. Ha senso? No davvero. Ma illustra che l'ereditarietà può sempre essere convertita in delega.

Discussione

Inheritance è caratterizzato dal fatto che una classe base può chiamare un metodo che viene sostituita in una sottoclasse. Questo è ad esempio l'essenza di template pattern. Tali cose non possono essere fatte facilmente con la delega. D'altra parte, questo è esattamente ciò che rende l'ereditarietà difficile da usare. Richiede una svolta mentale per capire dove si verifica il dispiegamento polimorfo e qual è l'effetto se i metodi vengono sovrascritti.

Esistono alcune insidie ​​relative all'ereditarietà e la fragilità che può essere introdotta nel progetto. Soprattutto se la gerarchia delle classi si evolve. Ci possono anche essere alcuni problemi con equality in hashCode e equals se si utilizza l'ereditarietà. Dall'altro lato, è ancora un modo molto elegante per risolvere alcuni problemi.

Inoltre, anche se l'eredità può essere sostituito con la delega, quella che si può sostenere che ancora raggiungono scopo diverso e si completano a vicenda - non trasmettono lo stesso intenzione che non viene catturato da puro tecnico equivalenza.

(La mia teoria è che quando qualcuno inizia a fare OO, siamo tentati di sovrasfruttare l'ereditarietà perché è percepito come una funzione della lingua .Poi impariamo la delega che è schema/approccio e impariamo a piacerci pure. Dopo qualche tempo, troviamo un equilibrio tra entrambi e sviluppo del senso dell'intuizione di cui si è meglio nel qual caso. Beh, come potete vedere, mi piace ancora entrambi, ed entrambi meritano una certa cautela prima di essere introdotto.)

Alcuni letteratura

Ereditarietà e la delega sono metodi alternativi per la definizione e la condivisione incrementale di . Si è comunemente ritenuto che la delega fornisca un modello più potente. Questo documento dimostra che esiste un modello di ereditarietà "naturale" che acquisisce tutte le proprietà della delega . Indipendentemente, alcuni vincoli sulla capacità della delegazione per acquisire l'ereditarietà sono dimostrati . Infine, un nuovo framework che acquisisce completamente entrambe le deleghe e l'ereditarietà è delineato e alcune delle ramificazioni di questo modello ibrido vengono esplorate.

Uno dei più problematici-nozioni più interessanti, e allo stesso tempo in orientato agli oggetti di programmazione è ereditarietà. L'ereditarietà è comunemente la considerata come la caratteristica che distingue la programmazione orientata agli oggetti da paradigma di programmazione, ma i ricercatori raramente concordano sul suo significato e utilizzo. [...]

causa del forte accoppiamento delle classi e la proliferazione dei membri della classe non necessari indotta per successione, il suggerimento di utilizzare composizione e delega ha invece diventare un luogo comune La presentazione di un corrispondente refactoring in letteratura può indurre a pensare che tale trasformazione sia un'impresa semplice. [...]

+0

Fa parte di questo estratto da un articolo pubblicato ? Se è così, sarei interessato a un link o doi. –

+2

I primi paragrafi sono miei ed è la mia opinione. Poi ci sono 3 riferimenti a documenti relativi all'argomento per il quale ho citato l'abstract. Il doi può essere trovato nei link che puntano tutti a portal.acm.org. Scusa se non era chiaro. Fammi sapere se non riesci a trovarli come pdf. – ewernli

+0

Grazie, apprezzo il chiarimento. –

3

Composizione non può rovinare la vita come fa l'eredità, quando i programmatori di pistola rapide cercano di risolvere i problemi con l'aggiunta di metodi ed estendere le gerarchie (piuttosto che dare un pensiero per le gerarchie naturali)

Composizione non può causare diamanti strani, che causano ai team di manutenzione di bruciare l'olio notturno graffiando le loro teste

L'ereditarietà era l'essenza della discussione nei modelli di progettazione GOF che non sarebbe stata la stessa se i programmatori usassero Composizione in primo luogo.

+0

Segnalami come un troll, ma ho fatto tutti questi errori prima di imparare da loro – questzen

1

Considera un toolkit gui.

Un controllo di modifica è una finestra, deve ereditare le funzioni di chiusura/abilitazione/disegno della finestra - non contiene una finestra.

Quindi un controllo rich text dovrebbe contenere le funzioni di comando di modifica salva/leggi/taglia/incolla che sarebbe molto difficile da usare se contenesse semplicemente una finestra e un controllo di modifica.

+0

Quando si discutono le classi della GUI, penso che sia troppo facile confondere la composizione visiva con la composizione della classe. Uno può creare IWindow che dichiara le firme per gli oggetti simili a finestre e Finestra per disegnare effettivamente le finestre e gestire gli eventi. Quindi creare IEditControl e EditControl, che implementano IWindow. EditControl semplicemente delega le sue responsabilità di IWindow all'oggetto Window. Pubblicamente, EditControl ha l'aspetto di qualsiasi altra finestra di IWindow e funziona come Window con gli abbellimenti EditControl. Infine, RichTextControl implementa IEditControl, delegando al proprio oggetto EditControl. Il modello è concatenabile. –

+1

Vero, la maggior parte degli esempi di ereditarietà (classi di impiegati, ecc.) Sono fatti meglio come composizione. Ma i toolkit GUI beneficiano davvero dell'ereditarietà (IMHO) –

+2

Rispettosamente: la mia azienda mantiene un enorme sistema legacy in .Net; Nell'esempio di MSFT con WinForms, gli sviluppatori originali usavano molta ereditarietà per implementare le classi dell'interfaccia utente. Abbiamo 18 caselle combinate uniche (anche se simili), 12 moduli di base e una gerarchia di ereditarietà fino a 8 livelli in profondità sulla CLI. Il nostro framework UI è così labirintico, così incoerente e fragile, l'attuale team di sviluppo è terrorizzato dal minimo cambiamento ad esso. Ma, spesso, tutto questo codice non soddisfa i nuovi bisogni ... La composizione avrebbe significato poter scegliere e selezionare le funzionalità in base alle esigenze, piuttosto che * generare * nuove combinazioni. –

1

Potrei sbagliarmi, ma lo dirò comunque, e se qualcuno ha una ragione per cui mi sbaglio, per favore basta rispondere con un commento e non farmi votare. C'è una situazione in cui posso pensare a dove l'ereditarietà sarebbe superiore alla composizione.

Supponiamo di avere una libreria di widget di origine chiusa che sto utilizzando in un progetto (il che significa che i dettagli di implementazione sono un mistero per me oltre a ciò che è documentato). Supponiamo ora che ogni widget abbia la possibilità di aggiungere widget secondari. Con l'ereditarietà, potrei estendere la classe Widget per creare un CustomWidget, quindi aggiungere CustomWidget come widget figlio di qualsiasi altro widget nella libreria. Poi il mio codice per aggiungere un CustomWidget sarebbe simile a questa:

Widget baseWidget = new Widget(); 
CustomWidget customWidget = new CustomWidget(); 
baseWidget.addChildWidget(customWidget); 

Molto pulito, e mantiene in linea con le convenzioni della biblioteca per l'aggiunta di widget figlio. Tuttavia, con la composizione, avrebbe dovuto essere qualcosa di simile:

Widget baseWidget = new Widget(); 
CustomWidget customWidget = new CustomWidget(); 
customWidget.addAsChildToWidget(baseWidget); 

Non come pulita, e rompe anche le convenzioni della biblioteca

Ora non sto dicendo che non poteva compire questo con la composizione (in effetti il ​​mio esempio mostra che puoi farlo molto chiaramente), non è l'ideale in tutte le circostanze e può portare a rompere le convenzioni e altre soluzioni alternative visivamente poco attraenti.

+2

Hai sottolineato che il widget si trovava in una libreria esterna, a sorgente chiusa. Se Widget.addChildWidget() accetta un argomento di tipo Widget, l'ereditarietà IMO è l'unica opzione valida. La biblioteca ha genitori che tengono traccia dei bambini, ma il tuo esempio di composizione richiede che CustomWidgets segua i loro genitori invece di essere rintracciati da loro. (Yuck!) Se Widget.addChildWidget() ha preso un argomento di tipo IWidget, la composizione potrebbe ancora funzionare, poiché CustomWidget potrebbe implementare IWidget, rimandando alcuni comportamenti a un widget privato. btw, Il vero potere di Interface Composition viene fornito con IOC e DI. –

-1

Sì. Si tratta di Run Time Type Identification (RTTI).

+0

Se hai bisogno di RTTI, francamente, stai violando il DIP. Ma io Avere un'interfaccia dichiarare una funzione 'getType()' o simile e implementare l'interfaccia. Ora hai la tua definizione di "tipo", che puoi plasmare in qualsiasi cosa tu voglia ... e le classi coinvolte non hanno bisogno di essere correlate l'una con l'altra. – cHao

Problemi correlati