Composizione su eredità ma


13

Sto cercando di insegnare a me stesso l'ingegneria del software e trovo alcune informazioni contrastanti che mi confondono.

Ho imparato OOP e quali sono le classi / interfacce astratte e come usarle, ma poi sto leggendo che si dovrebbe "favorire la composizione sull'eredità". Capisco la composizione quando una classe compone / crea un oggetto di un'altra classe per utilizzare / interagire con la funzionalità di quel nuovo oggetto.

Quindi la mia domanda è ... dovrei non usare classi e interfacce astratte? Non creare una classe astratta ed estendere / ereditare la funzionalità di quella classe astratta nelle classi concrete e invece comporre nuovi oggetti per usare la funzionalità dell'altra classe?

Oppure, dovrei usare la composizione E l'ereditarietà da classi astratte; li usi entrambi insieme? In tal caso, potresti fornire alcuni esempi di come funzionerebbe e dei suoi benefici?

Dato che mi sento più a mio agio con PHP, lo sto usando per migliorare le mie abilità OOP / SE prima di passare ad altre lingue e trasferire le mie abilità SE appena acquisite, quindi gli esempi che usano PHP sarebbero apprezzati.


2
"Favorire la composizione sull'eredità" è un'idea sciocca che ha esattamente lo stesso significato di "favore seghe su trapani". Sono due strumenti diversi che sono appropriati da usare in due diversi casi d'uso, e i tempi in cui puoi davvero usare uno dei due in modo intercambiabile sono in realtà piuttosto rari.
Mason Wheeler,

2
"Favorisci X su Y" non significa mai usare Y, pensa solo se X sarebbe meglio
Richard Tingle

2
"Favorire la composizione sull'ereditarietà" è espresso in modo più accurato come "Mai e poi mai usare l'ereditarietà delle classi", con il chiarimento che l'implementazione di un'interfaccia non è ereditarietà e se la lingua scelta supporta solo classi astratte, non interfacce, ereditando da un 100% la classe astratta può essere vista anche come "non ereditarietà".
David Arno,

1
@MasonWheeler, non ci sono casi d'uso validi per l'ereditarietà. È almeno "malvagio" una caratteristica come "goto".
David Arno,

1
@DavidArno È semplicemente ridicolo. L'ereditarietà è una delle funzionalità più utili e produttive mai sviluppate nell'intera storia della programmazione. Come per qualsiasi cosa utile, ci sono molti modi per abusarne, ma se usato correttamente aumenta enormemente la potenza e la produttività del tuo lavoro.
Mason Wheeler,

Risposte:


19

Composizione su ereditarietà significa che quando si desidera riutilizzare o estendere la funzionalità di una classe esistente, spesso è più appropriato creare un'altra classe che "avvolga" la classe esistente e ne utilizzi l'implementazione internamente. Il motivo decorativo è un esempio di questo.

L'ereditarietà non è un buon modo predefinito per gestire gli scenari di riutilizzo perché spesso si desidera utilizzare solo una parte della funzionalità di una classe di base e la sottoclasse non può supportare l'intero contratto della classe di base in un modo che soddisfi la sostituzione di Liskov principio .

Il metodo modello è un buon esempio di un caso in cui l'ereditarietà è appropriata.

Vedi questa risposta per le linee guida su come scegliere tra composizione ed eredità.


+1 per indicare che in caso di riutilizzo parziale di una classe, la porzione inutilizzata viene ignorata più chiaramente con la composizione che con l'eredità.
Lawrence,

4

Non ho abbastanza familiarità con PHP per darvi esempi concreti in quella lingua, ma ecco alcune linee guida che ho trovato utili.

Le interfacce / tratti sono utili per definire il comportamento in molte classi che potrebbero avere ben poco altro in comune.

Ad esempio, se si sta lavorando su un'API JSON, è possibile definire un'interfaccia che specifica due metodi: "toJson" e "toStatusCode". Ciò consentirebbe di scrivere un helper che può prendere qualsiasi oggetto che implementa questa interfaccia e convertirlo in una risposta Http.

Il più delle volte, è preferibile dirigere l'eredità, perché è meno probabile che soffra del fragile problema della classe base, poiché tende a gerarchie di classi superficiali.

L'ereditarietà diretta è meglio pensata come qualcosa che fai quando ci sono validi motivi per farlo, piuttosto che qualcosa che fai a meno che non ci siano validi motivi per non farlo.

Nei linguaggi che non supportano implementazioni predefinite nelle interfacce, può essere un modo ragionevole per condividere un set di funzionalità di base, ma di solito limita fortemente ciò che è possibile fare con le sottoclassi (l'ereditarietà multipla, quando disponibile, raramente vale la pena) . Spesso, trovo che valga la pena di scrivere i metodi di reindirizzamento della piastra di comando per utilizzare la composizione.

La composizione dovrebbe essere la tua strategia predefinita. Per quanto possibile, componi le interfacce e passale come argomenti del costruttore. Questo renderà la tua vita molto più semplice quando scrivi i test.

Evita il più possibile di lavorare con i singoli punti di riferimento globali, poiché confrontare l'output atteso e quello effettivo è un problema se una parte dell'output dipende dallo stato del clock di sistema.

Questo, ovviamente, non è sempre possibile. Ad esempio, il framework Web Play fornisce una libreria JSON che è fondamentalmente una lingua specifica del dominio. La modifica di questo sarebbe un'impresa importante, di cui, la sostituzione delle chiamate a "Json.prettyPrint" sarebbe solo una parte molto minore.


1

La composizione è quando una classe offre alcune funzionalità istanziando una classe (possibilmente interna) che già implementa questa funzionalità, invece di ereditare da quella classe.

Quindi, ad esempio, se hai una classe che modella una nave e ora ti viene detto che la tua nave dovrebbe offrire un eliporto, non è naturale derivare la tua nave da un eliporto, (duh!), Invece, dovresti avere la tua nave contiene una classe eliporto ed esponila con un Ship.getHelipad()metodo.

Negli anni (circa un decennio fa) le persone erano solite considerare l'eredità come un modo semplice e veloce per aggregare la funzionalità, quindi c'erano molti esempi del tipo di "nave che eredita dall'eliporto", che erano, ovviamente, molto zoppi.

Ma il detto "Favor Composizione sull'ereditarietà" è stato attentamente formulato per chiarire che questo è solo un suggerimento, non una regola. L'autore del detto è stato abbastanza attento da astenersi dal dire qualcosa del tipo " non userai mai l' eredità, solo la composizione". Ciò sta sostanzialmente portando all'attenzione della comunità dell'ingegneria del software il fatto che l'ereditarietà è stata abusata, mentre in molti casi la composizione produce disegni più chiari, eleganti e sostenibili dell'eredità.

Quindi, in sostanza, il detto "Favor Composizione sull'ereditarietà" suggerisce che ogni volta che ti trovi di fronte a "ereditare o comporre?" domanda, dovresti pensare a fondo qual è la strategia più adatta e che la maggior parte delle probabilità è che la strategia più adatta si traduca in composizione, non ereditarietà.

Ma poiché questa non è una regola, dovresti anche tenere presente che ci sono molti casi in cui l'eredità è più naturale. Se usi la composizione lì dove avresti dovuto usare l'eredità, molti mali ricadranno sul tuo codice.

Per tornare all'esempio della nave, se la tua nave ha bisogno di offrire un'interfaccia per interagire con una FloatingMachine, allora è più naturale derivarla da una FloatingMachineclasse astratta , che molto probabilmente potrebbe a sua volta derivare da un'altra Machineclasse astratta .

Ecco la regola empirica per la risposta alla composizione rispetto alla domanda di eredità:

La mia classe ha una relazione "is a" con l'interfaccia che deve esporre? Se sì, usa l'ereditarietà. In caso contrario, utilizzare la composizione.

Una nave "è una" macchina galleggiante e una macchina galleggiante "è una" macchina. Quindi, l'eredità va benissimo per quelli. Ma una nave non è, ovviamente, un eliporto. Quindi è meglio comporre la funzionalità dell'eliporto.

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.