Decorator
Coffee, CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar, CoffeeWithMilkSugarAndCream... Every combination needs a subclass.
Make each extra a wrapper with the same interface as the thing it wraps.
You need many optional extras that combine freely
The picture from life
A plain coffee costs 50. You ask for milk, then sugar. The barista does not invent a new drink for each mix; he wraps your coffee with each add-on, and every layer adds its own price and its own note on the cup. Layers can be stacked in any order, any number of times.
The problem
What goes wrong when you do not use it.
Coffee, CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar, CoffeeWithMilkSugarAndCream... Every combination needs a subclass. Five add-ons can mean thirty-two classes.
- extends (is a)
- You need many optional extras that combine freely
- You cannot (or should not) subclass
- Extras should be added or removed at run time
The idea
How the pattern fixes it.
Make each extra a wrapper with the same interface as the thing it wraps. It forwards calls to the inner object and adds its own bit before or after. Wrap again to add another extra.
- implements
Who is who
- Drink (interface)
- cost() and label().
- Coffee
- The plain thing being decorated.
- MilkDecorator, SugarDecorator
- Hold a Drink, add price and label, forward the rest.
Build it in steps
- 1Define the common interface.
- 2Write the plain object.
- 3Write a decorator that holds the interface and implements it.
- 4Wrap objects in any order you like.
Watch it run
Step through the calls. Tap an arrow to jump to it.
The drink is Sugar(Milk(Coffee)). The client asks the outer wrapper.
cost()The code
Python and Java. Try writing the classes yourself from the diagram first.
class Coffee:
def cost(self):
return 50
def label(self):
return "coffee"
class MilkDecorator:
def __init__(self, inner):
self.inner = inner # same interface as what it wraps
def cost(self):
return self.inner.cost() + 10
def label(self):
return self.inner.label() + " + milk"
class SugarDecorator:
def __init__(self, inner):
self.inner = inner
def cost(self):
return self.inner.cost() + 5
def label(self):
return self.inner.label() + " + sugar"
def demo():
drink = SugarDecorator(MilkDecorator(Coffee()))
return drink.cost(), drink.label()What you gain
- Combine extras freely, no subclass explosion
- Add or drop extras at run time
- Each extra does one thing
What it costs
- Many small wrapper objects
- Order can matter and is easy to get wrong
- Debugging through layers is harder
You have already met it in
- Java I/O: new BufferedReader(new InputStreamReader(...))
- Python decorators (@cache, @login_required) follow the same wrap idea
- Middleware stacks in web frameworks
Often mixed up with
Make it stick
Build a TextSource with read(), then decorators for Uppercase, Trim and Logging. Stack them in two different orders and see how the result changes.