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.