Decorator Pattern
The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Bates and Sierra — Head 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);
}
}