Decorator
Attach extra behavior to an object by wrapping it in another object that implements the same interface, stackable as many layers deep as you like.
The problem
You maintain a notification library. Version one had a Notifier class that emailed a
list of addresses. Then users wanted SMS for critical issues, and Slack for corporate
accounts, and Facebook for reasons of their own. Fine: one subclass per channel.
Then someone asks the obvious question. "If the building is on fire, can I have all of
them?" Now you are writing SMSAndSlackNotifier, and SMSAndSlackAndFacebookNotifier,
and the library is bloating faster than the client code that uses it.
The solution
The trouble is inheritance itself. It is static - you cannot change an object's class after construction - and in most languages a class gets exactly one parent, so combinations must become classes.
Composition escapes both. Build a wrapper: an object implementing the same interface as its target, holding a reference to it, forwarding every call and doing something extra before or after. Because the wrapper's field accepts anything satisfying the interface, a wrapper can hold another wrapper. Now behaviors are Lego bricks, snapped together at runtime in whatever combination the configuration file asked for.
- 1The client holds one DataSource reference and has no idea it is three objects deep.
- 2The outermost decorator does its work first, before delegating. Order in the stack is order of execution.
- 3It passes the transformed payload down through the same interface it was called with.
- 4The next layer adds its own contribution, oblivious to the layer above it.
- 5The bottom of the stack is the real component. It writes bytes and asks no questions.
- 6Control unwinds back through the stack. On read(), each layer would undo its work here instead.
- 7Every layer keeps the contract intact, so nobody upstream notices the detour.
- 8The client got a plain writeData() call, and the file on disk is compressed and encrypted.
Structure
The component interface is shared by everyone. The concrete component provides the base behavior. The base decorator stores a component and delegates blindly, existing mostly so that concrete decorators can be short. Concrete decorators override methods, run their own logic, and always call through.
Code
Compression and encryption layered over a file, chosen at runtime.
When to use it
- You need to assign extra responsibilities to objects at runtime without disturbing the code that uses them - logging, caching, retries, rate limiting, encryption, all layered over the same call.
- Inheritance is awkward or unavailable. A
finalclass cannot be subclassed, but it can certainly be wrapped. - One monolithic class implements many optional behaviors and wants to be split into small ones you can mix.
Pitfalls
- Order is semantics. Compress-then-encrypt produces a different file from encrypt-then-compress, and only one of them compresses well. Write the intended order down.
- Extraction is hard. Removing the third wrapper out of five means rebuilding the stack. If you need that regularly, you want a list of behaviors, not a chain of objects.
- Identity vanishes. Once wrapped,
instanceofchecks, equality and debugger output all talk about the wrapper. Stack traces become a tower ofwriteDataframes. - Ugly assembly code. The configuration block that builds the stack is rarely pretty. Hide it behind a factory or a builder rather than repeating it at every call site.
Don't confuse it with
- Adapter. An adapter deliberately changes the interface so foreign code can be called; a decorator preserves it and enhances what happens behind it. That symmetry is precisely why decorators nest and adapters generally do not.
- Proxy. Nearly the same structure, entirely different reason. A proxy is about control - lazy loading, permissions, caching - and typically manages the service object's whole lifecycle itself. Decorator stacks are assembled by the client on purpose.
- Composite. A decorator is a composite that happens to have one child. A composite aggregates results from many children; a decorator embellishes the one it has. Nothing stops you decorating a node inside a composite tree.
- Strategy. Decorator changes an object's skin from the outside; Strategy swaps its guts. If the variation is "which algorithm", reach for Strategy. If it is "what happens around the call", reach for Decorator.
- Chain of Responsibility. Both pass a call down a series of objects. A CoR handler is allowed to stop the chain dead; a decorator is not. Break the flow and you have changed patterns without meaning to.
Check yourself
Why is the wrapped field typed as the component interface rather than the concrete component class?