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èfondamentalmenteunaCoffeeComponent
quindil'interfaccia.HatremetodiprincipaligetSize()
,getName()
econtributionToCost()
.TienipresentecheilmetodocontributionToCost()
accettaunparametroPricingService
,perchélalogicadeicostièconilservizioPrezzi.AbstractCoffeeComponent
staimplementandol'interfacciaCoffeeComponent
perfornirel'implementazionedeimetodicomuni.DaAbstractCoffeeComponent
dueclassisonosottoclasse:Type
eIngredient
.Quindiespress,mochafaràriferimentoaType
classezucchero,illattesaràIngredient
istanze.L'ideaèchePricingServicehaunmetododiricercachepuòdareilcostosullabasedelCoffeeComponent
name
esize
.Ilrestodellagerarchiaèsolopermostrarecomeilconcettodicaffèpuòesserecodificato.DiseguitoèriportatoilcodiceperPricingService
,Client
eCoffee
.
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.