In che modo l'eredità e la composizione differiscono?

2

Mi sto interrogando sulle differenze tra ereditarietà e composizione esaminate con argomenti concreti relativi al codice.

In particolare il mio esempio era:

Inheritance :

class Do:
    def do(self):
        self.doA()
        self.doB()

    def doA(self):
        pass

    def doB(self):
        pass

class MyDo(Do):
    def doA(self):
        print("A")

    def doB(self):
        print("B")

x=MyDo()

vs Composizione :

class Do:
    def __init__(self, a, b):
        self.a=a
        self.b=b

    def do(self):
        self.a.do()
        self.b.do()

x=Do(DoA(), DoB())

(Nota per la composizione mi manca il codice quindi non è effettivamente più breve)

Puoi citare particolari vantaggi dell'uno o dell'altro?

Penso a:

    La composizione
  • è utile se prevedi di riutilizzare DoA () in un altro contesto
  • l'ereditarietà sembra più facile; nessun riferimento / variabile / inizializzazione aggiuntivi
  • Il metodo
  • doA può accedere alla variabile interna (che sia una cosa buona o cattiva :))
  • logica di gruppi ereditari A e B insieme; anche se potresti ugualmente introdurre un oggetto delegato raggruppato
  • l'ereditarietà fornisce una classe preimpostata per gli utenti; con la composizione dovresti incapsulare l'inizializzazione in una fabbrica in modo che l'utente debba assemblare la logica e lo scheletro
  • ...

In sostanza vorrei esaminare le implicazioni dell'ereditarietà rispetto alla composizione . Ho sentito spesso la composizione è preferita , ma mi piacerebbe capirlo con l'esempio.

Naturalmente posso sempre iniziare da uno e rifattorizzare più tardi.

    
posta Gerenuk 01.09.2012 - 11:47
fonte

5 risposte

4

Concettualmente, i modelli di composizione "consistono in" relazioni, mentre i modelli di ereditarietà "è un".

Utilizzando l'analogia dell'auto, "un'auto ha le ruote" è un esempio da manuale per la composizione, e ha senso avere una classe Wheel , e una classe Car con una proprietà wheels di tipo Wheel[] .

In teoria, un esempio di ereditarietà sarebbe "un camion è un veicolo": le proprietà comuni a tutti i veicoli possono essere implementate in classe Vehicle , mentre quelle specifiche per i camion possono essere implementate in classe Truck .

L'esempio del camion, tuttavia, illustra anche il problema dell'approccio ereditario: cosa succede se si deve rendere la classe del veicolo polimorfica non solo per lo scopo dei veicoli (passeggeri rispetto al trasporto merci), ma anche per il tipo di carburante? Dovresti creare quattro classi per coprire le autovetture e i veicoli merci, oltre a diesel e benzina. Aggiungi un'altra proprietà ortogonale e il numero di classi raddoppia di nuovo. Peggio ancora, devi decidere quale di queste proprietà ortogonali viene prima nella gerarchia di classi: è Vehicle - > DieselVehicle - > DieselFreightVehicle - > Truck , o è Vehicle - > FreightVehicle - > DieselFreightVehicle - > %codice%? Ad ogni modo, devi duplicare alcune funzionalità, sia le cose specifiche del trasporto, sia le cose specifiche del diesel. La soluzione è usare comunque la composizione: un veicolo ha un motore (che può essere alimentato a gasolio o gasonline) e un tipo di carico (che può essere passeggeri o merci); non hai più bisogno di una classe di camion, perché la tua classe di veicoli può già modellare tutti i tipi di veicoli combinando motori adatti e tipi di carico. Si noti che i componenti sono ancora polimorfici, ma l'oggetto contenitore non lo è. Il trucco è di mantenere il polimorfismo monodimensionale, cioè ogni modello di gerarchia polimorfica uno di molte proprietà ortogonali, come tipo di motore, tipo di carico, ecc.

Questo è il motivo per cui le persone dicono "favore la composizione sull'eredità"; ovviamente stai ancora ereditando, ma hai separato la tua eredità in varietà indipendenti.

    
risposta data 01.09.2012 - 14:23
fonte
1

Dipende dall'architettura e da cosa devi risolvere.

Nel tuo esempio un client del tuo codice se vuoi cambiare l'implementazione di doA, e doB con l'esempio di ereditarietà dovrebbe scrivere una classe che si estende da MyDo ma con la composizione, basta inizializzare con una classe che ha la stessa interfaccia .

Ora hai bisogno di cosa sono il codice di.

Supponiamo che la classe D prima della chiamata doA o doB chiami un altro metodo per eseguire validazioni o configurazioni e poi esegua doA, l'approccio dell'ereditarietà è buono. (in questo caso il client non chiama maiA se non un altro metodo che fa le cose e poi chiama doA)

Semplice esempio: Componente di classe. La finestra di classe estende Componente. Il componente di classe ha metodi che verrebbero utilizzati nella finestra di classe e supponiamo che tu cosa aggiungere un pulsante di chiusura sempre, quindi immagina di dover sovrascrivere il metodo addChildrens () per aggiungere quel pulsante. Ma non si chiama mai addChildern () che sta facendo il metodo di inizializzazione di Component Class, che ha già implementato un ciclo di vita. (Tieni presente che ciò potrebbe essere fatto con un approccio diverso)

Per situazione composita. MyDo usa direttamente doA o doB come servizi, quindi aspettati un'interfaccia, ma devi essere flessibile, supponi che i dati di lettura doA e doB formino qualche fonte. Quindi puoi avere diverse implementazioni di Do con le diverse fonti.

Semplice esempio: MyDo è una classe strumento di log e doA, metodo doB registra le informazioni. Puoi fare in modo che l'implementazione diventi esegua il login in diverse fonti.

I'm leave sono più esempi, ma in generale dipende da cosa devi fare dopo le lezioni.

    
risposta data 01.09.2012 - 14:41
fonte
1

Quando usi l'ereditarietà, una sottoclasse dipende dai dettagli di implementazione della sua superclasse. Se i dettagli di implementazione della superclasse cambiano, la sottoclasse potrebbe interrompersi anche se il suo codice non cambia. Quando usi la composizione non sei dipendente dall'implementazione di una classe, ma solo dalla sua interfaccia.

Se le classi Do e MyDo si trovano nello stesso pacchetto e sotto il controllo degli stessi programmatori, è generalmente sicuro utilizzare l'ereditarietà. Se si è a conoscenza delle dipendenze, è possibile testare insieme le classi quando l'implementazione della superclasse cambia. Se stai estendendo una classe che non è sotto il tuo controllo (come una classe di una libreria standard), vuoi usare la composizione in modo che una modifica nei dettagli di implementazione della classe della libreria non infranga il codice del tuo cliente.

    
risposta data 01.09.2012 - 14:46
fonte
0

Una cosa importante da ricordare è che l'ereditarietà non dovrebbe essere al solo scopo di riutilizzare il codice, ma usare delega / composizione per questo.

La composizione sarebbe quando una classe dice BroadbandConnection con un metodo chiamato connection (). Quindi il tuo manager dice che vogliamo aggiungere la crittografia, quindi crei una classe BroadbandConnectionWithEncryption. Il tuo istinto naturale potrebbe essere quello di utilizzare l'ereditarietà e quindi creare la nuova classe BroadbandConnectionWithEncryption derivata da BroadbandConnection.

Il problema è che il creatore della classe iniziale non l'aveva progettato per ereditarietà, quindi sarebbe necessario cambiare la sua definizione per rendere il metodo connection () virtuale in modo da poterne sovrascrivere il comportamento nella classe derivata. Questo non è sempre l'ideale. Un'idea migliore è quella di utilizzare la delega qui allo scopo di riutilizzare il codice.

class BroadBandConnection
{
   public:
   void Connection (string password)
   {
      //connection code.
   }
};

class BroadBandConnectionWithEndcryption
{
   public:
   void Connection (string password)
   {
       mbroadbandconnection.Connection(password);
       //now do some stuff to zero the memory or
       //do some encryption stuff
   }

   private:
   BroadBandConnection mbroadbandconnection;
};
    
risposta data 01.09.2012 - 12:12
fonte
-1

Eredità: i client gestiscono le istanze di MyDo (o altre sottoclassi). Scelgono il comportamento che desiderano selezionando una determinata sottoclasse e utilizzandola. La scelta migliore quando si desidera fornire al cliente un insieme di comportamenti predeterminati e immutabili.

Composizione: i client gestiscono direttamente le istanze di Do. Possono scegliere il comportamento che desiderano configurando l'oggetto o utilizzando un metodo di produzione appropriato. Possono anche cambiare il comportamento di un oggetto riconfigurandolo al volo. La scelta migliore quando vuoi dare al cliente la libertà di personalizzare un oggetto.

Immagina di essere un produttore di automobili. Hai tre modelli: Scoot, Zip e Fly. Tutti e tre sono basati su un telaio e un treno di trasmissione comuni (che chiamate Base internamente) ma hanno stili del corpo, interni diversi, ecc. Non vendete Base come prodotto; è solo la base per i modelli che vendi e che non sono nemmeno fabbricati come assemblaggio separato, è solo il set comune di caratteristiche che i tre modelli condividono. Scoot, Zip e Fly sono pacchetti completi: sono come sottoclassi di Base. Tutti e tre i modelli sono disponibili con varie opzioni installabili dal rivenditore: tetto apribile, stereo, ripetitori di razzi, ecc.

Quando decidi tra ereditarietà e composizione, considera ciò che vuoi fornire agli utenti. Vuoi che una determinata funzione venga inserita nel prodotto come il passaruota o vuoi che sia un'opzione installabile dal rivenditore, come un sistema di navigazione? A volte la scelta è ovvia; altre volte è una questione di scelte di design.

    
risposta data 01.09.2012 - 13:06
fonte

Leggi altre domande sui tag