JavaRush /Java Blog /Random-IT /Problemi di pizzeria. Costruttore vs decoratore.
CynepHy6
Livello 34
Великий Новгород

Problemi di pizzeria. Costruttore vs decoratore.

Pubblicato nel gruppo Random-IT

Descrizione del problema.

Dobbiamo scrivere un programma. Per una pizzeria che vuole preparare diversi tipi di pizza: Pollo, Americana, Carne, Hawaiana, Pepperoni, ecc. Vediamo quale modello e in quale scenario è adatto per risolvere questo problema. Tradizionalmente, il modello Builder viene utilizzato per risolvere i "problemi della pizza". Esistono anche esempi di utilizzo del modello Decorator, entrambi i modelli sono corretti, ma ci sono differenze nell'uso. Il Builder è un modello per la creazione di oggetti, mentre il Decorator viene utilizzato per modificare al volo l'oggetto finito. Cerchiamo di capirlo con degli esempi:

1. Generatore di modelli:

In questo caso la pizza si prepara con tutti gli ingredienti in una volta.
Lezione di pizza:
public class Pizza{ private float totalPrice = 0; private Size size; private Topping topping; private Crust crust; private Cheese cheese; public Size getSize(){ return size; } public void setSize(Size size){ this.size = size; } public Topping getTopping(){ return topping; } public void setTopping(Topping topping){ this.topping = topping; } public Crust getCrust(){ return crust; } public void setCrust(Crust crust){ this.crust = crust; } public Cheese getCheese(){ return cheese; } public void setCheese(Cheese cheese){ this.cheese = cheese; } public float getTotalPrice(){ return totalPrice; } public void addToPrice(float price){ this.totalPrice = totalPrice + price; } }
4 classi di enumerazione:
public enum Cheese { AMERICAN{ public float getCost(){ return 40; } }, ITALIAN { public float getCost(){ return 60; } }; public abstract float getCost(); } public enum Crust { THIN{ public float getCost(){ return 70; } } , STUFFED{ public float getCost(){ return 90; } }; public abstract float getCost(); } public enum Size { MEDIUM { public float getCost() { return 100; } }, LARGE { public float getCost() { return 160; } }; public abstract float getCost(); } public enum Topping { PEPPERONI { public float getCost(){ return 30; } }, CHICKEN{ public float getCost(){ return 35; } }, MUSHROOM{ public float getCost(){ return 20; } }; public abstract float getCost(); }
Classe PizzaBuilder:
public class PizzaBuilder { Pizza pizza = new Pizza(); public PizzaBuilder withTopping(Topping topping) { pizza.setTopping(topping); pizza.addToPrice(topping.getCost()); return this; } public PizzaBuilder withSize(Size size) { pizza.setSize(size); pizza.addToPrice(size.getCost()); return this; } public PizzaBuilder withCrust(Crust crust) { pizza.setCrust(crust); pizza.addToPrice(crust.getCost()); return this; } public Pizza build() { return pizza; } public double calculatePrice() { return pizza.getTotalPrice(); } }
Prova di classe:
public class PizzaBuilderTest { @Test public void shouldBuildThinCrustChickenPizza(){ Pizza pizza = new PizzaBuilder().withCrust(Crust.THIN).withTopping(Topping.CHICKEN).withSize(Size.LARGE).build(); assertEquals(Topping.CHICKEN,pizza.getTopping()); assertEquals(Size.LARGE,pizza.getSize()); assertEquals(Crust.THIN,pizza.getCrust()); assertEquals(265.0,pizza.getTotalPrice(),0); } }

2. Modello decoratore:

Il pattern Decoratore viene utilizzato per aggiungere o rimuovere funzionalità aggiuntive a un oggetto in modo dinamico senza influire sull'oggetto originale. Utilizzato quando si prepara prima la base della pizza e poi si aggiungono i vari ingredienti. Qui abbiamo bisogno di un'interfaccia (Pizza) per BasePizza (il componente fondamentale) che vogliamo decorare e di una classe PizzaDecorator che implementi effettivamente l'interfaccia.
Interfaccia pizza:
public interface Pizza { public String bakePizza(); public float getCost(); }
Implementazione in BasePizza:
public class BasePizza implements Pizza{ public String bakePizza() { return "Basic Pizza"; } public float getCost(){ return 100; } }
Lezione di PizzaDecorator:
public class PizzaDecorator implements Pizza { Pizza pizza; public PizzaDecorator(Pizza newPizza) { this.pizza = newPizza; } public String bakePizza() { return pizza.bakePizza(); } @Override public float getCost() { return pizza.getCost(); } }
2 decoratori: Funghi e Peperoni
public class Mushroom extends PizzaDecorator { public Mushroom(Pizza newPizza) { super(newPizza); } @Override public String bakePizza() { return super.bakePizza() + " with Mushroom Topings"; } @Override public float getCost() { return super.getCost()+80; } } public class Pepperoni extends PizzaDecorator { public Pepperoni(Pizza newPizza) { super(newPizza); } @Override public String bakePizza() { return super.bakePizza() + " with Pepperoni Toppings"; } @Override public float getCost() { return super.getCost()+110; } }
Prova di classe:
public class PizzaDecoratorTest { @Test public void shouldMakePepperoniPizza(){ Pizza pizza = new Pepperoni(new BasePizza()); assertEquals("Basic Pizza with Pepperoni Toppings",pizza.bakePizza()); assertEquals(210.0,pizza.getCost(),0); } }

Differenze

Modelli come Builder e Factory (e Abstract Factory) vengono utilizzati nella creazione di nuovi oggetti. E modelli come Decorator (noti anche come Structural Design Patterns) vengono utilizzati per l'estensibilità o per fornire modifiche strutturali a oggetti già creati. Entrambi i tipi di pattern promuovono principalmente la composizione tramite ereditarietà e le differenze non sono sufficientemente significative da giustificare l'utilizzo di un Builder anziché di un Decorator. Entrambi conferiscono il proprio comportamento quando vengono eseguiti, anziché ereditarlo. In un caso è meglio utilizzare il Builder, se vogliamo limitare la creazione di oggetti con determinate proprietà/funzioni. Ad esempio, ci sono 4-5 attributi che devono essere impostati prima di creare un oggetto, oppure vogliamo congelare la creazione di un oggetto finché non vengono ancora impostati determinati attributi. In poche parole, usalo al posto di un costruttore - come scrive Joshua Bloch in Java: Effective Programming, 2nd ed. Il builder fornisce gli attributi che dovrebbe avere l'oggetto generato ma nasconde come impostarli. Un decoratore viene utilizzato per aggiungere nuove proprietà a un oggetto esistente quando si crea un nuovo oggetto basato su di esso. Non ci sono restrizioni sul congelamento di un oggetto mentre vengono aggiunte tutte le sue funzionalità. Entrambi i modelli utilizzano la composizione e potrebbero sembrare simili. La differenza principale sta nel loro utilizzo.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION