Decorator Pattern

The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

Bates and SierraHead First Design Patterns

Class Diagram Explained

img /images/posts/design-patterns/decorator.png

ConcreteComponent object dynamically gets new behaviors, that is new methods are added to the component.

Decorator HAS-A or wraps a component having an instance variable referencing to a component.

ConcreteDecorators can extends the state of a component adding new properties, but also add new methods. A new method means a new behavior which is added by doing computation before or after an existing method in the component.

Key Points

  • Open-Closed Principle. Classes should be open for extension but closed for modification.

  • Design. It should make possible to extend the behavior without modifying the existing code. Composition and delegation allow to add new behaviors at runtime [Bates, Sierra].

  • Adding Behavior. Subclassing is not the only solution and is not always flexible, decorator classes wrap, decorate, the concrete components as many times you want adding functionality dynamically.

  • Core. Decorators change the behavior of their components by adding new functionality before and/or after method calls to the component [Bates, Sierra].

  • Jargon.

    • Composition means HAS-A, wrapping, holding a reference variable.
    • Behavior is synonym of method.
    • Delegation is a chain of method calls.

Scenario

Image to be the owner of a coffee shop. A customer can choose a coffee among many kinds and then add to it some condiments. There are basic condiments and sets of condiments ready to transform a coffee into a mocha, cappuccino and so on.

  • Goal. A bill should be produced considering the coffee and all the extra ingredients added.

  • Adding Behavior. The customer start from a normal coffee and adding condiments (new functionalities) changes the behavior of the coffee into something else such as mocha or cappuccino.

Implementation

  • Create a Coffee class and then different subclasses, one for each possible combination of condiments. What happens if a new condiment is added or removed from the list? if a recipe changes? Class explosion and it could be necessary to modified the code.

  • Use decorator pattern to dynamically add new behaviors.

public abstract class Coffee {

    private String description = "Simple coffe";

    private double cost;

    public Coffee() {
    }

    public Coffee(final String coffeeDescription) {
        description = coffeeDescription;
    }

    public String getDescription() {
        return description;
    }

    public abstract double getCost();
}
public abstract class CondimentDecorator extends Coffee {

    public abstract String getDescription();
}
public class Espresso extends Coffee {

    public Espresso() {
        super("Espresso");
    }

    @Override
    public final double getCost() {
        return 0.95;
    }
}
public class MilkDecorator extends CondimentDecorator {

    public static final double MILK_COST = .50;

    private Coffee coffee;

    public MilkDecorator(final Coffee coffeeBeverage) {
        coffee = coffeeBeverage;
    }

    @Override
    public final String getDescription() {
        return coffee.getDescription() + ", Milk";
    }

    @Override
    public final double getCost() {
        return coffee.getCost() + MILK_COST;
    }
}
public class DecoratorTest {

    private Coffee coffee;

    private CondimentDecorator condiment;

    @Before
    public void setUp() {
        coffee = new Espresso();
        condiment = new MilkDecorator(coffee);
    }

    @Test
    public void testDescription() throws Exception {
        assertEquals(condiment.getDescription(), "Espresso, Milk");
    }

    @Test
    public void testCost() throws Exception {
        assertEquals(condiment.getCost(), 1.45, 0.0);
    }
}