2015-09-20 24 views
9

Ho una gerarchia di classi come questa Beverage -> Coffee-> Latte. Dove è la superclasse abstract estesa da Coffee. La classe Coffee aggiunge un po 'di comportamento ma è anche abstract. Latte estende la classe Coffee ed è una classe concreta. Ho usato l'ereditarietà per aggiungere comportamenti qui. E l'ereditarietà ha degli inconvenienti come la visibilità dei metodi della superclasse, rendendo il codice fragile, il codice è strettamente accoppiato. Quindi, i principi di programmazione dettano Composition dovrebbero essere preferiti su Inheritance. Ma in questo casoLatte è un tipo di Coffee e Coffee è un tipo di Beverage che utilizzando composition per aggiungere il comportamento si sente sbagliato nonostante i suoi vantaggi. Quindi la domanda qui è L'intuizione dovrebbe prevalere sui principi di progettazione?Intuizione vs principi di progettazione

bevande:

public abstract class Beverage { 

    private final String name; 
    private final double price; 

    Beverage(String name, double price){ 
     this.name = name; 
     this.price = price; 
    } 

    public String getName() { 
     return name; 
    } 
    public double getPrice() { 
     return price; 
    } 

    public abstract void make(); 
    } 

caffè:

public abstract class Coffee extends Beverage { 

    public Coffee(String name, double price) { 
     super(name, price); 
    } 


    public final void make(){ 
     grindBeans(); 
     takeShot(); 
     frothMilk(); 
     addCondiments(); 
    } 

    public void grindBeans(){ 
     System.out.println("Grinding Beans..."); 
    } 

    public void takeShot(){ 
     System.out.println("Taking Shot...."); 
    } 

    public abstract void frothMilk(); 
    public abstract void addCondiments(); 


} 

Latte:

public class Latte extends Coffee { 

    public Latte() { 
     super("Latte", 4.0); 
    } 

    @Override 
    public void frothMilk() { 
     System.out.println("Frothing milk to create micro foam"); 

    } 

    @Override 
    public void addCondiments() { 
     // TODO Auto-generated method stub 

    } 

} 

EDIT: Aggiunta Sugar alla struttura esistente. Viene mostrato solo il nuovo codice.

public abstract class Beverage { 

private Sugar sugar; 

public Sugar getSugar() { 
    return sugar; 
} 

public void setSugar(Sugar sugar) { 
    this.sugar = sugar; 
} 

} 

Caffè:

public abstract class Coffee extends Beverage { 

public final void make(){ 
    grindBeans(); 
    takeShot(); 
    frothMilk(); 
    addSugar(); 
    addCondiments(); 
} 

public void addSugar(){ 
    Sugar sugar = super.getSugar(); 
    if(!(sugar instanceof NoSugar)){ 
     System.out.println("adding " + sugar.getTeaspoon() + " teaspoon sugar"); 
    } 
} 
+0

possibile duplicato di [Preferisci composizione su ereditarietà?] (Http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance) – jaco0646

+0

Questa domanda richiede un parere sull'intuizione rispetto ai principi di progettazione. – lit

risposta

6

Mentre composizione ha molti vantaggi rispetto eredità, non c'è niente di sbagliato con l'utilizzo di successione in cui ci si sente naturale (vale a dire in un rapporto veramente is-a). Se è naturale, vai avanti e usalo.

1

Penso che nei primi schemi di progettazione del libro l'esempio che usano è che la classe latte può avere diverse configurazioni. Utilizzando la composizione è possibile creare classi personalizzate in fase di esecuzione piuttosto che durante la progettazione

È possibile estendere solo da una classe e ciò può essere un fattore limitante a seconda di ciò che si sta facendo.

3

Composizione significa A ha una B, e l'ereditarietà A è una specie di B. Nel tuo caso sei al 100% giusto: l'ereditarietà deve essere usata: latee è un caffè e un caffè è una bevanda.

Perché lo consideri fragile? Ad esempio, latee dovrebbe avere tutte le proprietà del caffè, ma può implementarle in modo diverso. Niente di fragile qui - è un polimorfismo. Se vuoi limitare l'override dei metodi dei genitori, contrassegnali come finali.

Come esempio di composizione: ci sono auto e ruote. L'auto ha una ruota. L'auto ha bisogno di ruote per lavorare, ma sono oggetti completamente diversi. La macchina può essere aperta, chiusa, avviata, ecc. - la ruota non può. La ruota può ruotare e sgonfiare. Auto - impossibile.

P.S .: Oh, penso di aver capito cosa intendi per "fragile".Ecco un articolo su di esso http://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--which-one-should-you-choose-.html Per me questa cosa "composizione anziché ereditarietà" sembra ancora un hack OOP (in particolare, l'esempio nell'articolo: mela IS A frutto, non c'è modo intorno :)) Ogni volta che I vedrà questo hack in uso, probabilmente penserei a un progettista API per essere improvvisato.

+0

Sono d'accordo con te, ma nonostante sia naturale, penso che l'ereditarietà non sia adatta ad applicazioni complesse. per esempio, la risposta di Marcin Szymczak sopra ha senso. L'aggiunta di nuovi oggetti sarà difficile da mantenere. Così ho provato ad aggiungere un oggetto 'Sugar' usando la struttura ereditaria esistente (vedi la mia modifica al post).Ma in questo modo le mie lezioni sono sempre aperte alle modifiche che sono di nuovo una violazione del principio di progettazione. Che ne dici? –

1

Sì, a prima vista l'ereditarietà qui sembra andasse bene con l'argomento che ovunque sia naturale quindi ha senso avere ereditarietà. Il caffè è una bevanda e il latte è un caffè, quindi l'eredità va bene.

Anche se ha senso, cosa succede se abbiamo più tipi di caffè: cappuccino, frappe ecc. Quindi facciamo più sottotipi? Penso che allora possiamo usare un modello di decorazione in cui Latte estende il caffè e sarà esso stesso un caffè e così via. In questo caso faremo uso della composizione e dell'eredità entrambi insieme. Quindi, torcendo un po 'la discussione, in questo caso abbiamo bisogno sia di Composizione che di Eredità e non di/o.

Detto questo (e se hai comprato quello che ho detto sopra), cosa succede se continuiamo a decorare questo Latte diciamo avendo varianti Latte americano e francese Latte. Allora qual è la nostra classe base per un decoratore? È caffè o latte? Se scegliamo Coffee, American Latte è composto da Latte che è composto da caffè. La composizione sta tirando tutto insieme. Se, tuttavia, diciamo che è Latte, creiamo un'altra componente di base decoratore, ad esempio Latte. Poi abbiamo 2 livelli di decoratori - uno centrato attorno al caffè e un altro (1 livello sotto) centrato attorno a Latte che lo rende troppo confuso. Quindi, ha più senso avere Coffee come decoratore astratto e usare la composizione per portare Latte e poi latte francese/americano. Quindi, la composizione ha un vantaggio sull'eredità qui.

1

Se si sta sviluppando un sistema semplice in cui si hanno quattro sottotipi di caffè, si dovrebbe rimanere con l'ereditarietà.


Soluzione con ereditarietà ha i suoi problemi. Saranno visibili soprattutto quando aumenterà la quantità di sottotipi di caffè.

Il primo problema riguarda i metodi non implementati. Molto probabilmente avrai alcuni metodi che avranno solo un'implementazione vuota.

public class Espresso extends Coffee { 

    public Espresso() { 
     super("Espresso", 4.0); 
    } 

    @Override 
    public void frothMilk() { 
     // well I don't really need that method, so I will just write a comment 

    } 

    @Override 
    public void addCondiments() { 
     // that one is also unnecessary, does it mean that my inheritance tree is wrong? 

    } 

} 

Il secondo problema si verifica con l'esplosione esponenziale dei parametri nei caffè. Se si decide di avere Latte con zucchero si potrebbe finire con classe speciale per questo Latte, LatteWithSugar, che creerà anche nuovo *.WithSugar per ogni classe già esistente. Lo stesso con ogni modifica speciale della classe. Si prega di notare che la quantità di classi aumenta in modo esponenziale. Se si dispone di 8 tipi di caffè aggiungendo un parametro, 16 classi diventeranno sospette.


È una questione di scala.

Per la piccola app, potrebbe essere la sovrastruttura. Per la grande applicazione della caffetteria, ti farà risparmiare ore di manutenzione.

+0

Sono un po 'd'accordo con te, ma non ne sono completamente convinto. Così ho provato a farlo funzionare con la struttura corrente aggiungendo 'Sugar' per composizione in' Beverage'. E poi aggiungendo 'addSugar()' al metodo 'make()' in 'Coffee' e' Tea'. Ha funzionato senza dover creare classi aggiuntive, ma il lato negativo è che le mie classi sono aperte per la modifica. Riposo, penso sia buono. –

+0

Ho aggiunto un nuovo codice alla fine del post –