Ayumilove Decorator Design Pattern

Decorator Design Pattern Overview

  • Attaches additional responsibilities to an object dynamically.
  • Provides a flexible alternative to subclassing for extending functionality.
  • Minimizes class explosion (1 class for unique combination of functionality/stats).

Output for both Before and After

$0.99 : Dark Roast
$1.19 : Dark Roast with Mocha
$1.39 : Dark Roast with Double Mocha
$1.09 : Dark Roast with Steamed Milk
$1.49 : Dark Roast with Double Mocha and Steamed Milk
$1.99 : Espresso
$2.19 : Espresso with Mocha
$2.39 : Espresso with Double Mocha
$2.09 : Espresso with Steamed Milk
$2.49 : Espresso with Double Mocha and Steamed Milk

Before

class Beverage {
    String description() { return 'Unknown Beverage'; };
    float cost() { return 0; }
}

class DarkRoast extends Beverage {

    @Override
    String description() {
        return 'Dark Roast';
    }

    @Override
    float cost() {
        return 0.99;
    }
}

class DarkRoastWithDoubleMocha extends Beverage{

    @Override
    String description() {
        return 'Dark Roast with Double Mocha';
    }

    @Override
    float cost() {
        return 0.99 + 0.20 + 0.20;
    }
}

class DarkRoastWithDoubleMochaAndSteamedMilk extends Beverage{

    @Override
    String description() {
        return 'Dark Roast with Double Mocha and Steamed Milk';
    }

    @Override
    float cost() {
        return 0.99 + 0.20 + 0.20 + 0.10;
    }
}

class DarkRoastWithMocha extends Beverage{

    @Override
    String description() {
        return 'Dark Roast with Mocha';
    }

    @Override
    float cost() {
        return 0.99 + 0.20;
    }
}

class DarkRoastWithSteamedMilk extends Beverage{

    @Override
    String description() {
        return 'Dark Roast with Steamed Milk';
    }

    @Override
    float cost() {
        return 0.99 + 0.10;
    }
}

class Espresso extends Beverage{

    @Override
    String description() {
        return 'Espresso';
    }

    @Override
    float cost() {
        return 1.99;
    }
}

class EspressoWithDoubleMocha extends Beverage{

    @Override
    String description() {
        return 'Espresso with Double Mocha';
    }

    @Override
    float cost() {
        return 1.99 + 0.20 + 0.20;
    }
}

class EspressoWithDoubleMochaAndSteamedMilk extends Beverage{

    @Override
    String description() {
        return 'Espresso with Double Mocha and Steamed Milk';
    }

    @Override
    float cost() {
        return 1.99 + 0.20 + 0.20 + 0.10;
    }
}

class EspressoWithMocha extends Beverage{

    @Override
    String description() {
        return 'Espresso with Mocha';
    }

    @Override
    float cost() {
        return 1.99 + 0.20;
    }
}

class EspressoWithSteamedMilk extends Beverage{

    @Override
    String description() {
        return 'Espresso with Steamed Milk';
    }

    @Override
    float cost() {
        return 1.99 + 0.10;
    }
}

import java.text.DecimalFormat
class Simulator {
    static void main(String[] args) {
        display(new DarkRoast());
        display(new DarkRoastWithMocha());
        display(new DarkRoastWithDoubleMocha());
        display(new DarkRoastWithSteamedMilk());
        display(new DarkRoastWithDoubleMochaAndSteamedMilk());
        display(new Espresso());
        display(new EspressoWithMocha());
        display(new EspressoWithDoubleMocha());
        display(new EspressoWithSteamedMilk());
        display(new EspressoWithDoubleMochaAndSteamedMilk());
    }

    static void display(Beverage b) {
        DecimalFormat df = new DecimalFormat("#.##");
        System.out.println('$' + df.format(b.cost()) + ' : ' + b.description());
    }
}

After

class Beverage {
    String description() { return 'Unknown Beverage'; };
    float cost() { return 0; }
}

class Condiment extends Beverage {
    Beverage beverage;
    Condiment(Beverage b) {
        beverage = b;
    }

    @Override
    float cost() {
        return beverage.cost();
    }
}

class DarkRoast extends Beverage {

    @Override
    String description() {
        return 'Dark Roast';
    }

    @Override
    float cost() {
        return 0.99;
    }
}

class Espresso extends Beverage {

    @Override
    String description() {
        return 'Espresso';
    }

    @Override
    float cost() {
        return 1.99;
    }
}

class Mocha extends Condiment {

    Mocha(Beverage b) {
        super(b);
    }

    @Override
    String description() {
        return beverage.description() + ', Mocha';
    }

    @Override
    float cost() {
        return beverage.cost() + 0.20;
    }
}

class SteamedMilk extends Condiment {

    SteamedMilk(Beverage b) {
        super(b);
    }

    @Override
    String description() {
        return beverage.description() + ', Steamed Milk';
    }

    @Override
    float cost() {
        return beverage.cost() + 0.10;
    }
}

class PrettyPrintBeverage extends Condiment {

    private static final String[] tuple = ['', 'Double', 'Triple', 'Quad', 'Max'];

    PrettyPrintBeverage(Beverage b) {
        super(b);
    }

    @Override
    String description() {
        String[] descriptions = beverage.description().split(',');
        if (descriptions.size() < 2) return beverage.description();

        HashMap ingredients = getIngredients(descriptions)
        StringBuilder ingredientDesc = getIngredientDescription(ingredients)
        return ingredientDesc.toString();
    }

    private StringBuilder getIngredientDescription(HashMap ingredients) {
        int totalIngredients = ingredients.size();
        StringBuilder ingredientDesc = new StringBuilder('');
        int tupleSize = tuple.size();
        int count = 0;
        for (Map.Entry ingredient : ingredients.entrySet()) {
            count++;
            if (count == 2) {
                ingredientDesc.append(' with ');
            } else if (count > 2 && totalIngredients - count == 0){
                ingredientDesc.append(' and ');
            } else if (count > 2) {
                ingredientDesc.append(', ');
            };
            int quantity = ingredient.getValue();
            String quantityName = quantity > tupleSize ? tuple[tupleSize - 1] : tuple[quantity - 1];
            String ingredientName = ingredient.getKey();
            String tempDesc = quantityName.trim() == '' ? ingredientName : quantityName + ' ' + ingredientName;
            ingredientDesc.append(tempDesc);
        }
        return ingredientDesc;
    }

    private HashMap getIngredients(String[] descriptions) {
        int totalDescriptions = descriptions.size();
        Map ingredients = new HashMap();
        for (int i = 0; i < totalDescriptions; i++) {
            String desc = descriptions[i].trim();
            if (ingredients[desc] == null) {
                ingredients[desc] = 1;
            } else {
                ingredients[desc] += 1;
            }
        }
        return ingredients;
    }
}

import java.text.DecimalFormat
class Simulator {
    static void main(String[] args) {
        display(new DarkRoast());
        display(new Mocha(new DarkRoast()));
        display(new Mocha(new Mocha(new DarkRoast())));
        display(new SteamedMilk(new DarkRoast()));
        display(new SteamedMilk(new Mocha(new Mocha(new DarkRoast()))));

        display(new Espresso());
        display(new Mocha(new Espresso()));
        display(new Mocha(new Mocha(new Espresso())));
        display(new SteamedMilk(new Espresso()));
        display(new SteamedMilk(new Mocha(new Mocha(new Espresso()))));
    }

    static void display(Beverage b) {
        DecimalFormat df = new DecimalFormat("#.##");
        PrettyPrintBeverage prettyPrint = new PrettyPrintBeverage(b);
        System.out.println('$' + df.format(b.cost()) + ' : ' + prettyPrint.description());
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *