"Preferisci la composizione rispetto all'eredità": l'unica ragione per difendersi dalle modifiche alla firma?


13

Questa pagina sostiene la composizione sull'eredità con il seguente argomento (riformulato nelle mie parole):

Una modifica della firma di un metodo della superclasse (che non è stata ignorata nella sottoclasse) provoca ulteriori modifiche in molti punti quando utilizziamo Eredità. Tuttavia, quando usiamo Composizione, la modifica aggiuntiva richiesta è solo in un unico posto: la sottoclasse.

È davvero l'unica ragione per favorire la composizione rispetto all'eredità? Perché, in tal caso, questo problema può essere facilmente alleviato applicando uno stile di codifica che promuove l'override di tutti i metodi della superclasse, anche se la sottoclasse non modifica l'implementazione (ovvero, inserendo le sostituzioni fittizie nella sottoclasse). Mi sto perdendo qualcosa qui?




2
La citazione non dice che è "l'unica" ragione.
Tulains Córdova,

3
La composizione è quasi sempre migliore, poiché l'ereditarietà generalmente aggiunge complessità, accoppiamento, lettura del codice e riduce la flessibilità. Ma ci sono notevoli eccezioni, a volte una o due classi base non fanno male. Ecco perché " preferisce " piuttosto che " sempre ".
Mark Rogers,

Risposte:


27

Mi piacciono le analogie, quindi eccone una: hai mai visto una di quelle TV con un videoregistratore incorporato? Che ne dici di quello con un videoregistratore e un lettore DVD? O uno che ha un Blu-ray, un DVD e un videoregistratore. Oh, e ora tutti sono in streaming, quindi dobbiamo progettare un nuovo televisore ...

Molto probabilmente, se possiedi un televisore, non ne hai uno come quello sopra. Probabilmente la maggior parte di quelli non è nemmeno mai esistita. Molto probabilmente hai un monitor o un set che ha numerosi input in modo da non aver bisogno di un nuovo set ogni volta che c'è un nuovo tipo di dispositivo di input.

L'ereditarietà è come la TV con il videoregistratore incorporato. Se hai 3 comportamenti di tipi diversi che vuoi usare insieme e ognuno ha 2 opzioni per l'implementazione, hai bisogno di 8 classi diverse per rappresentare tutte quelle. Man mano che aggiungi più opzioni i numeri esplodono. Se invece usi la composizione, eviti quel problema combinatorio e il design tenderà ad essere più estensibile.


6
C'erano molti televisori con videoregistratore e lettori DVD integrati tra la fine degli anni '90 e l'inizio degli anni 2000.
JAB

@JAB e molti (la maggior parte?) TV venduti oggi supportano lo streaming, del resto.
Casey,

4
@JAB E nessuno di loro può avere il proprio lettore DVD venduto per aiutare ad acquistare un lettore Bluray
Alexander - Reinstate Monica,

1
@JAB Giusto. La frase "Hai mai visto uno di quei televisori con un videoregistratore incorporato?" implica che esistono.
JimmyJames,

4
@Casey Perché ovviamente non ci sarà mai un'altra tecnologia che sostituirà questa, giusto? Scommetto che una di quelle TV supporta anche input da dispositivi esterni nel caso in cui qualcuno voglia usare qualcos'altro senza acquistare una nuova TV. O meglio, pochi acquirenti colti sono disposti ad acquistare un televisore privo di tali input. La mia premessa non è che questi approcci non esistano e neppure direi che l'eredità non esiste. Il punto è che tali approcci sono intrinsecamente meno flessibili della composizione.
JimmyJames,

4

No non lo è. Nella mia esperienza, la composizione sull'ereditarietà è il risultato del pensare al tuo software come a un gruppo di oggetti con comportamenti che parlano tra loro / oggetti che forniscono servizi l'un l'altro invece di classi e dati .

In questo modo, hai alcuni oggetti che forniscono servizi. Li usi come blocchi predefiniti (molto simili a Lego) per abilitare altri comportamenti (ad es. Un UserRegistrationService utilizza i servizi forniti da EmailerService e UserRepository ).

Quando ho costruito sistemi più grandi con questo approccio, ho usato l'ereditarietà in pochissimi casi. Alla fine della giornata l'eredità è uno strumento molto potente che dovrebbe essere usato con cautela e solo dove appropriato.


Vedo la mentalità Java qui. OOP riguarda i dati che vengono compressi insieme alle funzioni che agiscono su di essi - ciò che stai descrivendo è meglio definito "moduli". Hai ragione nel dire che evitare l'ereditarietà è una buona idea quando si ripropongono oggetti come moduli, ma trovo inquietante che tu sembri raccomandarlo come il modo preferito di usare gli oggetti.
Brilliand,

1
@Brilliand Se solo sapessi quanto codice java è scritto nello stile mentre descrivi e quanto orrore questo è ... Smalltalk ha introdotto il termine OOP ed era progettato attorno agli oggetti che ricevono messaggi. : "Il calcolo dovrebbe essere visto come una capacità intrinseca di oggetti che possono essere invocati in modo uniforme inviando messaggi." Non vi è alcuna menzione di dati e funzioni che vi operano. Scusa, ma secondo me ti sbagli gravemente. Se si trattasse solo di dati e funzioni, si potrebbe felicemente continuare a scrivere strutture.
Marstato,

@Tsar sfortunatamente, anche se Java supporta metodi statici, la cultura Java no
k_g

@Brilliand Chi se ne frega di cosa "OOP"? Ciò che conta è come puoi usarlo e i risultati del suo utilizzo in quei modi.
user253751

1
Dopo la discussione con @Tsar: le classi Java non devono essere istanziate, e in effetti classi come UserRegistrationService e EmailService di solito non devono essere istanziate - le classi senza dati associati funzionano meglio come classi statiche (e come tali, sono irrilevanti per l'originale domanda). Eliminerò alcuni dei miei precedenti commenti, ma la mia critica originale alla risposta di Marsato è valida.
Brilliand,

4

"Preferire la composizione rispetto all'eredità" è solo una buona euristica

Dovresti considerare il contesto, poiché questa non è una regola universale. Non prenderlo nel senso che non usi mai l'eredità quando puoi fare composizione. In tal caso, lo risolveresti vietando l'ereditarietà.

Spero di chiarire questo punto lungo questo post.

Non proverò a difendere i meriti della composizione da solo. Che considero fuori tema. Parlerò invece di alcune situazioni in cui lo sviluppatore può considerare l'eredità che sarebbe meglio usare la composizione. In quella nota, l'eredità ha i suoi meriti, che considero anche fuori tema.


Esempio di veicolo

Sto scrivendo di sviluppatori che cercano di fare cose stupide, per scopi narrativi

Facciamo una variante degli esempi classici che alcuni corsi OOP usano ... Abbiamo a Vehicle classe, allora noi deriviamo Car, Airplane, Balloone Ship.

Nota : se devi fondare questo esempio, fai finta che si tratti di tipi di oggetti in un videogioco.

Poi Car e Airplanepotrebbe avere un codice comune, perché entrambi possono rotolare sulla ruota a terra. A tale scopo, gli sviluppatori potrebbero prendere in considerazione la creazione di una classe intermedia nella catena di eredità. Tuttavia, in realtà esiste anche un codice condiviso tra Airplanee Balloon. Potrebbero prendere in considerazione la possibilità di creare un'altra classe intermedia nella catena dell'eredità.

Pertanto, lo sviluppatore sarebbe alla ricerca di ereditarietà multipla. Nel punto in cui gli sviluppatori sono alla ricerca di eredità multipla, il design è già andato storto.

È meglio modellare questo comportamento come interfacce e composizione, in modo da poterlo riutilizzare senza dover imbattersi in eredità di più classi. Se gli sviluppatori, ad esempio, creano la FlyingVehiculeclasse. Direbbero che Airplaneè una FlyingVehicule(eredità di classe), ma potremmo invece dire che Airplaneha un Flyingcomponente (composizione) ed Airplaneè una IFlyingVehicule(eredità dell'interfaccia).

Utilizzando le interfacce, se necessario, possiamo avere ereditarietà multiple (di interfacce). Inoltre, non si esegue l'accoppiamento a un'implementazione particolare. Aumentare la riusabilità e la testabilità del codice.

Ricorda che l'eredità è uno strumento per il polimorfismo. Inoltre, il polimorfismo è uno strumento per la riusabilità. Se puoi aumentare la riusabilità del tuo codice usando la composizione, allora fallo. Se non sei sicuro di quale sia la composizione che fornisca una migliore riusabilità, "Preferisci la composizione rispetto all'eredità" è una buona euristica.

Tutto ciò senza menzionare Amphibious.

In effetti, potremmo non aver bisogno di cose che cadono da terra. Stephen Hurn ha un esempio più eloquente nei suoi articoli "Favor Composition Over Inheritance", parte 1 e parte 2 .


Sostituibilità e incapsulamento

Dovrebbero Aereditare o comporre B?

Se Aè una specializzazione di Bciò che dovrebbe soddisfare il principio di sostituzione di Liskov , l'eredità è praticabile, persino desiderabile. Se ci sono situazioni in cuiA non è una sostituzione valida per Ballora non dovremmo usare l'ereditarietà.

Potremmo essere interessati alla composizione come una forma di programmazione difensiva, per difendere la classe derivata . In particolare, una volta che inizi a utilizzarlo Bper altri scopi diversi, potrebbe esserci la pressione per modificarlo o estenderlo per renderlo più adatto a tali scopi. Se esiste il rischio che Bpotrebbe esporre metodi che potrebbero determinare uno stato non valido A, dovremmo utilizzare la composizione anziché l'ereditarietà. Anche se siamo l'autore di entrambi Be A, è una cosa in meno di cui preoccuparsi, quindi la composizione facilita la riusabilità diB .

Potremmo anche sostenere che se ci sono funzionalità Bche Anon sono necessarie (e non sappiamo se tali funzionalità potrebbero risultare in uno stato non valido A, sia nella presente implementazione che in futuro), è una buona idea usare la composizione invece dell'eredità.

La composizione ha anche i vantaggi di consentire la commutazione delle implementazioni e di facilitare la derisione.

Nota : ci sono situazioni in cui vogliamo usare la composizione nonostante la sostituzione sia valida. Archiviamo tale sostituibilità utilizzando interfacce o classi astratte (che una da usare quando è un altro argomento) e quindi usiamo la composizione con l'iniezione di dipendenza dell'implementazione reale.

Infine, ovviamente, c'è l'argomento secondo cui dovremmo usare la composizione per difendere la classe genitore perché l'eredità interrompe l'incapsulamento della classe genitore:

L'ereditarietà espone una sottoclasse ai dettagli dell'implementazione del genitore, si dice spesso che "l'ereditarietà rompe l'incapsulamento"

- Modelli di progettazione: elementi di software riutilizzabile orientato agli oggetti, Gang of Four

Bene, questa è una classe genitore mal progettata. Ecco perché dovresti:

Progettare per l'eredità o vietarlo.

- Efficace Java, Josh Bloch


Problema Yo-Yo

Un altro caso in cui la composizione aiuta è il problema Yo-Yo . Questa è una citazione da Wikipedia:

Nello sviluppo del software, il problema yo-yo è un anti-pattern che si verifica quando un programmatore deve leggere e comprendere un programma il cui grafico dell'ereditarietà è così lungo e complicato che il programmatore deve continuare a sfogliare tra molte diverse definizioni di classe per seguire il flusso di controllo del programma.

Puoi risolvere, ad esempio: La tua classe Cnon erediterà dalla classe B. Invece la tua classe Cavrà un membro di tipo A, che può o meno essere (o avere) un oggetto di tipo B. In questo modo non programmerai contro i dettagli di implementazione di B, ma contro il contratto che l'interfaccia (di) Aoffre.


Esempi contrari

Molti framework favoriscono l'ereditarietà rispetto alla composizione (che è l'opposto di ciò di cui abbiamo discusso). Lo sviluppatore può farlo perché ha messo molto lavoro nella sua classe di base che averlo implementato con la composizione avrebbe aumentato le dimensioni del codice client. A volte questo è dovuto alle limitazioni della lingua.

Ad esempio, un framework PHP ORM può creare una classe base che utilizza metodi magici per consentire la scrittura di codice come se l'oggetto avesse proprietà reali. Invece il codice gestito dai metodi magici andrà al database, eseguirà una query per il particolare campo richiesto (forse memorizzalo nella cache per richieste future) e lo restituirà. Farlo con la composizione richiederebbe al client di creare proprietà per ciascun campo o di scrivere una versione del codice dei metodi magici.

Addendum : Esistono altri modi per consentire l'estensione degli oggetti ORM. Pertanto, non credo che l'ereditarietà sia necessaria in questo caso. È più economico.

Per un altro esempio, un motore di videogioco può creare una classe base che utilizza codice nativo in base alla piattaforma di destinazione per eseguire il rendering 3D e la gestione degli eventi. Questo codice è complesso e specifico per la piattaforma. Sarebbe costoso e soggetto a errori per l'utente sviluppatore del motore gestire questo codice, in realtà, ciò è parte del motivo dell'utilizzo del motore.

Inoltre, senza la parte di rendering 3D, ecco come funzionano i framework di widget. Questo ti libera dal preoccuparti della gestione dei messaggi del sistema operativo ... in effetti, in molte lingue non puoi scrivere tale codice senza una qualche forma di supporto nativo. Inoltre, se dovessi farlo, restringerebbe la tua portabilità. Invece, con l'ereditarietà, purché lo sviluppatore non rompa la compatibilità (troppo); sarai in grado di trasferire facilmente il tuo codice su qualsiasi nuova piattaforma che supporteranno in futuro.

Inoltre, considera che molte volte vogliamo solo sostituire alcuni metodi e lasciare tutto il resto con le implementazioni predefinite. Se avessimo usato la composizione avremmo dovuto creare tutti quei metodi, anche se solo per delegare all'oggetto spostato.

Con questo argomento, c'è un punto in cui la composizione può essere peggiore per manutenibilità rispetto all'ereditarietà (quando la classe base è troppo complessa). Tuttavia, ricorda che la manutenibilità dell'eredità può essere peggiore di quella della composizione (quando l'albero dell'ereditarietà è troppo complesso), che è ciò a cui mi riferisco nel problema yo-yo.

Negli esempi presentati, gli sviluppatori intendono raramente riutilizzare il codice generato tramite ereditarietà in altri progetti. Ciò mitiga la riusabilità ridotta dell'uso dell'eredità anziché della composizione. Inoltre, utilizzando l'ereditarietà, gli sviluppatori del framework possono fornire molto codice facile da usare e da scoprire.


Pensieri finali

Come puoi vedere, la composizione presenta alcuni vantaggi rispetto all'ereditarietà in alcune situazioni, non sempre. È importante prendere in considerazione il contesto e i diversi fattori coinvolti (come riusabilità, manutenibilità, testabilità, ecc.) Per prendere la decisione. Torna al primo punto: "Preferisco la composizione per l'eredità" è un solo buona euristica.

Potresti anche notare che molte delle situazioni che descrivo possono essere risolte in qualche modo con Tratti o Mixin. Purtroppo, queste non sono caratteristiche comuni nella grande lista di lingue e di solito hanno un costo in termini di prestazioni. Per fortuna, i loro popolari metodi di estensione cugina e le implementazioni predefinite mitigano alcune situazioni.

Ho un post recente in cui parlo di alcuni dei vantaggi delle interfacce sul perché richiediamo interfacce tra UI, Business e accesso ai dati in C # . Aiuta a disaccoppiare e facilita la riusabilità e la testabilità, potresti essere interessato.


Uh ... .Net framework utilizza diversi tipi di flussi ereditati per fare il suo lavoro relativo al flusso. Funziona incredibilmente bene ed è super facile da usare ed estendere. Il tuo esempio non è molto buono ...
T. Sar,

1
@TSar "è super facile da usare ed estendere" Hai mai provato a collegare lo stream standard a Debug? Questa è stata la mia unica esperienza cercando di estendere i loro flussi. Non è stato facile Fu un incubo che finì con me scavalcare esplicitamente ogni singolo metodo della classe. Estremamente ripetitivo. E se guardi il codice sorgente .NET, sovrascrivere tutto in modo esplicito è praticamente come lo fanno. Quindi non sono convinto che sia mai così facile estenderlo.
jpmc26,

La lettura e la scrittura di una struttura da un flusso viene eseguita meglio con funzioni libere o multimetodi.
user253751

@ jpmc26 Mi dispiace che la tua esperienza non sia stata molto positiva. Tuttavia, non ho avuto problemi a utilizzare o implementare elementi relativi allo stream per cose diverse.
T. Sar,

3

Normalmente l'idea alla base di Composizione-sopra-ereditarietà è quella di fornire una maggiore flessibilità di progettazione e non ridurre la quantità di codice che è necessario modificare per propagare un cambiamento (che trovo discutibile).

L'esempio su Wikipedia fornisce un esempio migliore della potenza del suo approccio:

Definendo un'interfaccia di base per ciascun "pezzo-composizione" potresti facilmente creare vari "oggetti composti" con una varietà che potrebbe essere difficile da raggiungere con l'eredità da sola.


Quindi questa sezione fornisce un esempio di composizione, giusto? Lo sto chiedendo perché non è ovvio nell'articolo.
Utku,

1
Sì, "Composizione sull'ereditarietà" non significa che non hai interfacce, se guardi l'oggetto Player puoi vedere che si adatta bene in una gerarchia, ma il codice (scusa la brutalità) non proviene dall'eredità, ma dalla composizione con qualche lezione che fa il lavoro
lbenini

2

La linea: "Preferisci la composizione rispetto all'eredità" non è altro che una spinta nella giusta direzione. Non ti salverà dalla tua stessa stupidità e in realtà ti darà la possibilità di peggiorare le cose. Questo perché c'è molto potere qui.

Il motivo principale per cui è necessario dirlo è perché i programmatori sono pigri. Così pigri prenderanno qualsiasi soluzione che significhi meno battitura a tastiera. Questo è il principale punto di forza dell'eredità. Scrivo meno oggi e qualcun altro viene fregato domani. E allora, voglio andare a casa.

Il giorno dopo il capo insiste che io uso la composizione. Non spiega perché, ma insiste su di esso. Quindi prendo un'istanza di ciò che stavo ereditando, espongo un'interfaccia identica a ciò che stavo ereditando e implemento tale interfaccia delegando tutto il lavoro a quella cosa da cui stavo ereditando. È tutta una battuta a morte che avrebbe potuto essere fatta con uno strumento di refactoring e mi sembra inutile, ma il capo lo voleva, quindi lo faccio.

Il giorno dopo chiedo se questo è ciò che si voleva. Cosa pensi dice il capo?

A meno che non ci fosse la necessità di essere in grado di cambiare dinamicamente (in fase di esecuzione) ciò che veniva ereditato (vedi modello di stato), ciò rappresentava una grande perdita di tempo. Come può il capo dirlo ed essere ancora a favore della composizione rispetto all'eredità?

In aggiunta a ciò, non fare nulla per evitare rotture dovute ai cambiamenti nella firma del metodo, questo manca del tutto il più grande vantaggio della composizione. A differenza dell'eredità, sei libero di creare un'interfaccia diversa . Un altro più appropriato a come verrà utilizzato a questo livello. Sai, astrazione!

Ci sono alcuni piccoli vantaggi nel sostituire automaticamente l'eredità con composizione e delega (e alcuni svantaggi), ma mantenere il cervello spento durante questo processo manca un'enorme opportunità.

L'indirizzamento è molto potente. Usalo saggiamente.


0

Ecco un'altra ragione: in molti linguaggi OO (sto pensando a quelli come C ++, C # o Java qui), non c'è modo di cambiare la classe di un oggetto dopo averlo creato.

Supponiamo di creare un database di dipendenti. Abbiamo una classe Employee di base astratta e iniziamo a ricavare nuove classi per ruoli specifici: Ingegnere, Guardia di Sicurezza, Camionista e così via. Ogni classe ha un comportamento specializzato per quel ruolo. Va tutto bene.

Quindi un giorno un ingegnere viene promosso a direttore tecnico. Non è possibile riclassificare un ingegnere esistente. Invece, devi scrivere una funzione che crea un responsabile tecnico, copiando tutti i dati dall'ingegnere, elimina l'ingegnere dal database, quindi aggiunge il nuovo responsabile tecnico. E devi scrivere una tale funzione per ogni possibile cambio di ruolo.

Ancora peggio, supponiamo che un camionista vada in congedo per malattia a lungo termine e che una guardia di sicurezza si offra di fare un camion part time per compilare. Ora devi inventare una nuova classe di guardiani di sicurezza e camionisti solo per loro.

Rende la vita molto più semplice creare una classe Employee concreta e trattarla come un contenitore a cui è possibile aggiungere proprietà, come il ruolo lavorativo.


Oppure si crea una classe Employee con un puntatore a un Elenco di ruoli e ogni lavoro effettivo estende il ruolo. Il tuo esempio potrebbe essere fatto per usare l'eredità in un modo molto buono e ragionevole senza troppo lavoro.
T. Sar,

0

L'ereditarietà fornisce due cose:

1) Riutilizzo del codice: può essere realizzato facilmente attraverso la composizione. E infatti, si ottiene meglio attraverso la composizione in quanto preserva meglio l'incapsulamento.

2) Polimorfismo: può essere realizzato attraverso "interfacce" (classi in cui tutti i metodi sono puramente virtuali).

Un argomento forte per l'utilizzo dell'ereditarietà non di interfaccia è quando il numero di metodi richiesti è elevato e la sottoclasse vuole solo modificarne un piccolo sottoinsieme. Tuttavia, in generale la tua interfaccia dovrebbe avere impronte molto piccole se ti abboni al principio della "responsabilità singola" (dovresti), quindi questi casi dovrebbero essere rari.

Avere lo stile di codifica che richiede l'override di tutti i metodi della classe genitore non evita comunque di scrivere un gran numero di metodi pass-through, quindi perché non riutilizzare il codice attraverso la composizione e ottenere il vantaggio aggiuntivo dell'incapsulamento? Inoltre, se la superclasse dovesse essere estesa aggiungendo un altro metodo, lo stile di codifica richiederebbe l'aggiunta di quel metodo a tutte le sottoclassi (e ci sono buone probabilità che stiamo violando la "segregazione dell'interfaccia" ). Questo problema si sviluppa rapidamente quando si iniziano a ereditare più livelli.

Infine, in molti linguaggi il test di unità basato su oggetti "di composizione" è molto più semplice rispetto al test di unità basato su "ereditarietà" sostituendo l'oggetto membro con un oggetto simulato / test / fittizio.


0

È davvero l'unica ragione per favorire la composizione rispetto all'eredità?

No.

Ci sono molte ragioni per favorire la composizione rispetto all'eredità. Non esiste un'unica risposta corretta. Ma lancerò un altro motivo per favorire la composizione rispetto all'eredità nel mix:

Favorire la composizione rispetto all'eredità migliora la leggibilità del codice , che è molto importante quando si lavora su un grande progetto con più persone.

Ecco come ci penso: quando leggo il codice di qualcun altro, se vedo che hanno esteso una classe, chiederò che comportamento stanno cambiando nella superclasse?

E poi esaminerò il loro codice, cercando una funzione di sostituzione. Se non ne vedo nessuno, allora lo classifico come un uso improprio dell'eredità. Avrebbero dovuto usare la composizione, se non altro per salvare me (e ogni altra persona del team) da 5 minuti di lettura del loro codice!

Ecco un esempio concreto: in Java ilJFrame classe rappresenta una finestra. Per creare una finestra, è necessario creare un'istanza di JFramee quindi chiamare le funzioni su quell'istanza per aggiungere elementi ad essa e mostrarli.

Molti tutorial (anche quelli ufficiali) raccomandano di estenderti in JFramemodo da poter trattare le istanze della tua classe come istanze di JFrame. Ma imho (e l'opinione di molti altri) è che questa è una cattiva abitudine da prendere! Invece, dovresti semplicemente creare un'istanza diJFrame e chiamare funzioni su quell'istanza. Non è assolutamente necessario estenderlo JFramein questa istanza, poiché non si modifica alcun comportamento predefinito della classe genitore.

D'altra parte, un modo per eseguire la pittura personalizzata è estendere la JPanelclasse e sovrascrivere la paintComponent()funzione. Questo è un ottimo uso dell'eredità. Se sto esaminando il tuo codice e vedo che hai esteso JPanele ignorato ilpaintComponent() funzione, saprò esattamente quale comportamento stai cambiando.

Se sei solo tu a scrivere il codice da solo, allora potrebbe essere altrettanto facile usare l'eredità come lo è usare la composizione. Ma fai finta di essere in un ambiente di gruppo e che altre persone leggeranno il tuo codice. Favorire la composizione rispetto all'eredità rende il codice più facile da leggere.


L'aggiunta di un'intera classe factory con un singolo metodo che restituisce un'istanza di un oggetto in modo da poter utilizzare la composizione anziché l'ereditarietà non rende il tuo codice più leggibile ... (Sì, hai bisogno di questo se hai bisogno di una nuova istanza ogni volta viene chiamato un metodo particolare.) Ciò non significa che l'ereditarietà sia buona, ma la composizione non migliora sempre la leggibilità.
jpmc26,

1
@ jpmc26 ... perché mai hai bisogno di una classe di fabbrica solo per creare una nuova istanza di una classe? E in che modo l'ereditarietà migliora la leggibilità di ciò che stai descrivendo?
Kevin Workman,

Non ti ho esplicitamente indicato un motivo per tale fabbrica nel commento sopra? È più leggibile perché comporta meno codice da leggere . È possibile ottenere lo stesso comportamento con un singolo metodo astratto nella classe base e alcune sottoclassi. (Avresti avuto un'implementazione diversa della tua classe composta per ognuna, comunque.) Se i dettagli della costruzione dell'oggetto sono fortemente legati alla classe base, non troverà molto altro uso nel tuo codice, riducendo ulteriormente i vantaggi di creare una classe separata. Quindi mantiene parti del codice strettamente correlate tra loro.
jpmc26,

Come ho detto, questo non significa necessariamente che l'ereditarietà sia buona, ma esemplifica come la composizione non migliora la leggibilità in alcune situazioni.
jpmc26,

1
Senza vedere un esempio specifico, è difficile commentare davvero. Ma quello che hai descritto mi sembra un caso di ingegneria eccessiva. Hai bisogno di un'istanza di una classe? La newparola chiave ti dà uno. Comunque grazie per la discussione.
Kevin Workman,
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.