Quale modello può essere usato al posto di queste istruzioni if in starbuzz (head first design patterns-Decorator pattern)

4

Lettura dei primi disegni di modelli. Sono venuto al capitolo 3, il pattern Decorator,

Il pattern Decorator è molto chiaro per me. ma uno dei "affila la tua matita"

Our friends at Starbuzz have introduced sizes to their menu. You can now order a coffee in tall, grande, and venti sizes (translation: small, medium, and large). Starbuzz saw this as an intrinsic part of the coffee class, so they’ve added two methods to the Beverage class: setSize() and getSize(). They’d also like for the condiments to be charged according to size, so for instance, Soy costs 10¢, 15¢ and 20¢ respectively for tall, grande, and venti coffees.How would you alter the decorator classes to handle this change in requirements?

alla fine del capitolo, hanno aggiunto lo snippet di codice per risolvere questa parte questo snippet di codice è stato aggiunto a ogni CondimentDecorator di classe concreta

public double cost() {
    double cost = beverage.cost();
    if (getSize() == Beverage.TALL) {
        cost += .10;
    } else if (getSize() == Beverage.GRANDE) {
        cost += .15;
    } else if (getSize() == Beverage.VENTI) {
        cost += .20;
    }
    return cost;
}

aggiungi prima Size enum alla classe Beverage , quindi fai if..else per verificare le diverse dimensioni

Non penso che questo snippet di codice sarà estensibile in futuro, e se starbuzz chiedesse di aggiungere più dimensioni, porterà a cambiare ogni singola classe concreta CondimentDecorator , e sarà un incubo di manutenzione

Ho pensato di refactoring questa parte di essere anche un modello di progettazione, ma io sono ancora nuovo in questo argomento.

Qual è il modello più appropriato che possiamo applicare per risolvere questo problema?

La gerarchia di classi completa può essere trovata in link

    
posta Melad Ezzat 20.08.2018 - 12:20
fonte

6 risposte

5

Invece di cercare uno schema completo con un repository connesso al database o ai file di configurazione, suggerirò una piccola correzione per situazioni simili, perché non penso che le tue funzionalità siano diventate abbastanza complesse da investire più sforzi in esso. Inoltre, uno schema più complesso potrebbe non essere più la risposta giusta una volta che avrai aggiunto più cose, quindi tienilo semplice.

Per questo cercherò una possibilità offerta da Java:

1: enum può avere attributi.

public enum Beuverage{
    TALL(0.10), GRANDE(0.15), VENITI(0.20);
    public final int cost;//If i remember well it is mandatory private
    private Beuverage(int cost){...}
}

Questo suppone lo stesso prezzo supplementare per tutte le bevande.

2: Differenze di prezzo per ogni tipo di bevande per ogni taglia: sostituisci se / else di Map<Size, int> internamente in ogni decoratore di componenti

Questo è praticamente un esplicativo sefl.

    
risposta data 20.08.2018 - 17:11
fonte
1

In questo esempio, dobbiamo memorizzare una tabella di costi aggiuntivi per ogni condimento diverso alle varie dimensioni. Nel tuo esempio Soy costa rispettivamente 10 ¢, 15 ¢ e 20 ¢ per caffè alto, grande e venti. Forse i costi corrispondenti per il caramello sono 12 ¢, 17 ¢ e 22 ¢ e diversi costi extra per tutti gli altri condimenti. Questa informazione deve essere memorizzata da qualche parte, e il posto logico è in ciascuna delle concrete classi di decoratore.

Presumibilmente il punto delle lezioni di decoratore è di essere in grado di calcolare il costo totale del caffè finale, prima visitando il caffè base e poi lavorare all'esterno con ogni condimento aggiungendo il suo extra. Non si può prescindere dal fatto che ogni decoratore deve conoscere le dimensioni per poter svolgere il proprio lavoro.

Quindi la soluzione suggerita nel libro non è male per la situazione, nonostante i problemi di manutenzione che hai identificato correttamente.

EDIT: Dopo aver letto la risposta ottimale di Walfrat per Java, ho rimosso alcune parti inutili della mia risposta originale. Senza essere specifici della lingua, ci sono diversi metodi per rimuovere i blocchi di istruzioni if-else / switch, come ad esempio il polimorfismo (vedi Rob), Composito (vedi Sikorski), Strategia (ancora Rob), ma includendo anche Visitatore non menzionato. Ci sono anche diverse opzioni specifiche per la lingua come Maps ed Enums in Java, array associativi in PHP e array di funzioni in C, C ++ a seconda di quanto sia complicata l'azione in ogni punto di decisione. Forse c'è una possibilità per una domanda generale davvero buona qui!

Ma non dimenticarti che l'esempio proviene da un libro di testo il cui scopo è quello di illustrare Decorator Pattern in azione. Questo è un buon esempio che mostra quanto sia potente il pattern, ma anche come inizia a stressare in determinate situazioni .

In pratica, nessuno sano di mente si prenderebbe la briga di usare Decorator per sommare costi come questo, e ci sono approcci molto più facili. Decorator viene utilizzato meglio in situazioni più complesse, come l'elaborazione di documenti o la creazione di interfacce utente complesse, mediante la funzionalità di stratificazione.

    
risposta data 20.08.2018 - 14:41
fonte
0

È possibile creare implementazioni basate su dimensioni di CondimentDecorator. PER ESEMPIO. LargeCondimentDecorator, SmallCondimentDecorator ecc. Ogni metodo di costo restituisce il costo appropriato per quella dimensione.

Quindi assicurati che l'appropiato sia iniziato per la dimensione invece di usare l'enumerazione di tipo.

    
risposta data 20.08.2018 - 14:52
fonte
0

Una cosa che farei sicuramente sarebbe il refactoring nel suo stesso metodo. Stiamo aggiungendo il costo in base alla sua dimensione, no? Quindi, dovrebbe avere il suo metodo:

public double cost() { 
    return beverage.cost() + additionalCostBasedOnSize(); 
} 

private double additionalCostBasedOnSize() {
    switch(getSize()){
        case Beverage.TALL: return .10;
        case Beverage.GRANDE: return .15;
        case Beverage.VENTI: return .20; 
        default: return 0;
    }
}

Da questo punto di partenza puoi trovare alcuni schemi comuni tra diversi condimenti per raggrupparli insieme

    
risposta data 20.08.2018 - 15:17
fonte
0

In questo caso sono andato per alcuni pattern di psuedo Composite e la logica dei costi è del tutto in una classe diversa. Il diagramma delle classi è il seguente:

QuindiognicosachevainCoffeeèfondamentalmenteunaCoffeeComponentquindil'interfaccia.HatremetodiprincipaligetSize(),getName()econtributionToCost().TienipresentecheilmetodocontributionToCost()accettaunparametroPricingService,perchélalogicadeicostièconilservizioPrezzi.AbstractCoffeeComponentstaimplementandol'interfacciaCoffeeComponentperfornirel'implementazionedeimetodicomuni.DaAbstractCoffeeComponentdueclassisonosottoclasse:TypeeIngredient.Quindiespress,mochafaràriferimentoaTypeclassezucchero,illattesaràIngredientistanze.L'ideaèchePricingServicehaunmetododiricercachepuòdareilcostosullabasedelCoffeeComponentnameesize.Ilrestodellagerarchiaèsolopermostrarecomeilconcettodicaffèpuòesserecodificato.DiseguitoèriportatoilcodiceperPricingService,ClienteCoffee.

PricingService.java

packagecoffee;importjava.util.HashMap;importjava.util.Map;importjava.util.Objects;/***PricingideallywouldbequeryingdatabaseforcostsbuthereHashMapwilldo*/publicclassPricingService{privatestaticfinalclassCostComponent{privateStringcomponentName;privateSizecomponentSize;publicCostComponent(Stringname,Sizesize){this.componentName=name;this.componentSize=size;}@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;CostComponentcostComponent1=(CostComponent)o;returncomponentName.equalsIgnoreCase(costComponent1.componentName)&&componentSize==costComponent1.componentSize;}@OverridepublicinthashCode(){returnObjects.hash(componentName,componentSize);}}privatefinalstaticMap<CostComponent,Double>costs=newHashMap<>();static{costs.put(newCostComponent("Espresso", Size.TALL), 10.0);
        costs.put(new CostComponent("Rum", Size.TALL), 40.0);
    }


    public double costOf(CoffeeComponent component){
        return costs.getOrDefault(new CostComponent(component.getName(), component.getSize()),0.0);
    }

}

Coffee.java

package coffee;

import java.util.HashSet;
import java.util.Set;

/**
 * Container class - because we want coffee
 */
public class Coffee {

    private final Type type;
    private Set<Ingredient> ingredients;

    public Coffee(Type type) {
        this.type = type;
        this.ingredients = new HashSet<>();
        this.ingredients.addAll(type.getDefaultIngredients());
    }

    public void addIngredient(Ingredient ingredient){
        this.ingredients.add(ingredient);
    }

    /**
     * Propagate size to all igredients and type
     */
    public void setSize(Size size){
        for(Ingredient ingredient: ingredients){
            ingredient.setSize(size);
        }
        type.setSize(size);
    }

    /**
     * Aggregate of all pieces for cost
     */
    public double cost(PricingService pricingService){
        double cost = 0.0;
        for(Ingredient ingredient: ingredients){
            cost+=ingredient.contributionToCost(pricingService);
        }
        cost+=type.contributionToCost(pricingService);
        return cost;
    }

}

Client.java

package coffee;

public class Client {


    public static void main(String[] args) {
        PricingService starbuzz = new PricingService();

        //Order an Espresso
        Coffee espresso = new Coffee(new Type("Espresso"));
        /**
         * Here new Type, new Ingredient is done, ideally this will also be some sort of lookup using Factory pattern
         */
        //add sugar
        espresso.addIngredient(new Ingredient("Sugar"));
        //add rum :)
        espresso.addIngredient(new Ingredient("Rum"));
        //make it large
        espresso.setSize(Size.TALL);

        double cost = espresso.cost(starbuzz);
        System.out.println("Cost : " + cost);
    }
}

CoffeeComponent.java

package coffee;

public interface CoffeeComponent {

    String getName();

    void setSize(Size size);

    /**
     * Default lookup, implementations can override if wanted
     */
    default double contributionToCost(PricingService pricingService){
        return pricingService.costOf(this);
    }

    /**
     * By default coffee is normal unless explicitly stated
     */
    default Size getSize(){
        return Size.NORMAL;
    }

}

Ingredient.java

package coffee;

/**
 * Sugar, milk, soy etc come here
 */
public class Ingredient extends AbstractCoffeeComponent {

    public Ingredient(String name) {
        super(name);
    }
}

Type.java

package coffee;

import java.util.Collections;
import java.util.Set;

/**
 * Espresso, Mocha etc types come here
 */
public class Type extends AbstractCoffeeComponent {

    public Type(String name) {
        super(name);
    }

    /**
     * Default ingredients that come with this type
     */
    public Set<Ingredient> getDefaultIngredients(){
        return Collections.emptySet();
    }
}

AbstractCoffeeComponent.java

package coffee;

public abstract class AbstractCoffeeComponent implements CoffeeComponent {

    protected final String name;
    protected Size size;

    public AbstractCoffeeComponent(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void setSize(Size size) {
        this.size = size;
    }

    @Override
    public Size getSize() {
        return size;
    }
}

Come puoi vedere se devi aggiungere altri tipi, ingredienti, dimensioni aggiungi solo più righe in PricingService % s Hashmap Spero che questo ti aiuti. Sebbene io sia d'accordo con @bcperth, l'esempio è stato quello di mostrare pro e contro del pattern Decorator.

    
risposta data 20.08.2018 - 16:09
fonte
0

Potresti usare il modello di strategia. Pseudo codice:

inteface SizePricingPolicy
getCost(Size)

class DefaultSizePricing:SizePricingPolicy{
int getCost(Size){
 case Beverage.TALL: return .10;
        case Beverage.GRANDE: return .15;
        case Beverage.VENTI: return .20; 
        default: return 0;
}

}

Ogni decoratore passerebbe un'istanza della politica dei prezzi tramite l'iniezione del costruttore. Il metodo cost () dei decoratori chiamerebbe il metodo getCost () passando la dimensione come argomento come parte del calcolo del costo.

Il vantaggio è che la politica dei prezzi delle dimensioni può essere sostituita invece che modificata e poiché i decoratori mantengono solo riferimenti all'interfaccia di SizePricingPolicy sono isolati dalle modifiche al prezzo delle dimensioni.

    
risposta data 20.08.2018 - 16:08
fonte

Leggi altre domande sui tag