Composizione sull'eredità ma

10

Sto cercando di insegnarmi ingegneria del software e confrontarmi con alcune informazioni contrastanti che mi stanno confondendo.

Ho imparato l'OOP e quali sono le classi / interfacce astratte e come usarle, ma poi sto leggendo che si dovrebbe "favorire la composizione sull'ereditarietà". Capisco che la composizione sia 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 è ... Non dovrei usare classi e interfacce astratte? Per non creare una classe astratta ed estendere / ereditare la funzionalità della classe astratta nelle classi concrete, e invece, basta comporre nuovi oggetti per utilizzare la funzionalità dell'altra classe?

Oppure, dovrei usare la composizione E ereditare dalle classi astratte; usandoli entrambi in combinazione tra loro? In tal caso, potresti fornire alcuni esempi di come funzionerebbe e dei relativi vantaggi?

Dato che sono più a mio agio con PHP, lo sto usando per migliorare le mie abilità OOP / SE prima di passare ad altri linguaggi e trasferire le mie nuove competenze SE acquisite, quindi gli esempi che usano PHP sarebbero molto apprezzati.

    
posta MikeMason 08.11.2015 - 02:48
fonte

3 risposte

17

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 la utilizza internamente. Il pattern Decorator è un esempio di questo.

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

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

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

    
risposta data 08.11.2015 - 04:59
fonte
4

Non ho familiarità con PHP per darti esempi concreti in quel linguaggio, ma qui ci sono alcune linee guida che ho trovato utili.

Interfacce / tratti sono utili per definire il comportamento in molte classi che possono avere molto poco in comune.

Ad esempio, se stai lavorando su un API JSON, puoi definire un'interfaccia che specifica due metodi: "toJson" e "toStatusCode". Questo ti permetterebbe di scrivere un helper che possa prendere qualsiasi oggetto che implementa questa interfaccia e convertirlo in una risposta Http.

La maggior parte delle volte, è preferibile dirigere l'ereditarietà, perché è meno probabile che soffra del fragile problema della classe base, in quanto tende verso gerarchie di classi poco profonde.

Eredità diretta è meglio pensarla 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.

Nelle lingue che non supportano le implementazioni predefinite nelle interfacce, potrebbe essere un modo ragionevole per condividere un insieme di funzionalità di base, ma in genere limita pesantemente ciò che si può fare con le sottoclassi (l'ereditarietà multipla, quando disponibile, raramente vale il dolore). Spesso, trovo che valga la pena di scrivere i metodi di reindirizzamento boilerplate per usare invece la composizione.

Composition dovrebbe essere la tua strategia predefinita. Per quanto possibile, componi le interfacce e passale come argomenti del costruttore. Ciò renderà la tua vita molto più semplice durante la scrittura dei test.

Evita il più possibile di lavorare con i singleton globali, in quanto il confronto tra l'output previsto e quello effettivo è un problema giusto se parte dell'output dipende dallo stato dell'orologio di sistema.

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

    
risposta data 08.11.2015 - 04:58
fonte
1

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

Quindi, ad esempio, se hai una classe che modella una nave, e ti viene ora detto che la tua nave dovrebbe offrire un posto di atterraggio, non è naturale per la tua nave derivare da un eliporto, (duh!), invece, dovresti fare in modo che la tua nave contenga una classe di eliporto e esponila tramite un metodo Ship.getHelipad() .

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

Ma il motto "Favorisci la successione all'ereditarietà" è stato accuratamente formulato per chiarire che questo è solo un suggerimento, non una regola. L'autore del motto è stato abbastanza prudente da astenersi dal dire qualcosa come "tu mai usare l'ereditarietà, solo composizione". Questo in sostanza sta portando all'attenzione della comunità dell'ingegneria del software il fatto che l'ereditarietà sia stata abusata, mentre in molti casi la composizione produce disegni più chiari, eleganti e mantenibili rispetto all'ereditarietà.

Quindi, in sostanza, il detto "Favorisci l'ereditarietà" suggerisce che ogni volta che ti trovi di fronte al "ereditare o comporre?" domanda, dovresti riflettere seriamente quale sia la strategia più adatta e che la maggior parte delle probabilità è che la strategia più adatta si rivelerà essere la composizione, non l'ereditarietà.

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

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

Ecco la regola generale per la risposta alla domanda di composizione vs. ereditarietà:

Does my class have an "is a" relationship to the interface that it needs to expose? If yes, use inheritance. If not, use composition.

Una nave "è una" macchina mobile e una macchina mobile "è una" macchina ". Quindi l'ereditarietà è perfetta per quelli. Ma una nave non è, ovviamente, un eliporto. Quindi è meglio comporre la funzionalità dell'eliporto.

    
risposta data 08.11.2015 - 16:27
fonte

Leggi altre domande sui tag