Card 01/ 08
Decorator Pattern: Adding Behaviour by Wrapping an Object
Wrap a coffee in milk, then in sugar, and each wrapper is still a coffee.
The Decorator pattern adds behaviour to an object by wrapping it in another object with the same interface. The wrapper, the decorator, does its own work and passes each call on to the object inside, and because it has the same interface it can itself be wrapped. This card covers the combination problem that calls for it, the intent, and when it is the right tool.
The problem appears when features combine freely. A coffee can have milk, sugar, both or neither, and each changes the description and the price. With subclasses, every combination is a class: CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar. Three options make eight classes, and a fourth makes sixteen.
Without the patternthe combinations become classes
class Coffee: ...
class CoffeeWithMilk(Coffee): ...
class CoffeeWithSugar(Coffee): ...
class CoffeeWithMilkAndSugar(Coffee): ...
class CoffeeWithMilkAndSyrup(Coffee): ...Each new option doubles the classes, and the combinations are fixed when the code is written.
With the patternwrappers chosen at run time
order = Coffee()
if wants_milk:
order = Milk(order)
if wants_sugar:
order = Sugar(order)
print(order.describe(), order.cost())Each option is one class, and options combine in any order while the program runs.
The Gang of Four state the intent as: attach additional responsibilities to an object dynamically, providing a flexible alternative to subclassing for extending functionality. "Dynamically" means at run time, to one object, rather than to every object of a class when the code is written.
Think of clothing in layers: a shirt, then a jumper, then a coat, and you are still a person who can be dressed further. The analogy breaks in one place: you can take your layers off in any order, while a decorator stack is fixed once it is built, because each wrapper holds the one inside it.
Decorator is one of seven structural patterns, the family that composes classes and objects into larger structures.
| Pattern | What it does |
|---|---|
| Adapter | Gives an existing object the interface a client expects. |
| Bridge | Separates an abstraction from its implementation so both can vary. |
| Composite | Treats a tree of objects and a single object the same way. |
| Decorator (this topic) | Adds behaviour by wrapping an object in another with the same interface. |
| Facade | Puts one simple interface in front of a complex subsystem. |
| Flyweight | Shares the common part of many small objects to save memory. |
| Proxy | Stands in for an object to control access to it. |
Use it when
- Features can be combined freely, so subclasses would be needed for every combination.
- Behaviour must be added to one object at run time and not to the whole class.
- The class cannot be subclassed, or you do not own it, but it has an interface you can implement.
Avoid it when
- There is one fixed extra behaviour, which a subclass or a small change to the class gives more directly.
- The code relies on the object's concrete class or identity, which a wrapper hides.
- Stacks grow deep enough that a failure means stepping through many small wrappers to find it.
← → move through sections and cards