Home Design Patterns Decorator
Decorator
Structural Gang of Four“Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.”
Gamma, Helm, Johnson & Vlissides, p. 175Purpose
A Decorator adds behavior to one individual object, at runtime, without changing its class and without changing the interface the client sees. That last part is what separates it from most of the other structural patterns: the client cannot tell whether it is holding the original object or a stack of four wrappers around it.
The problem it solves is combinatorial. Suppose a report can be compressed, encrypted, and logged. With subclassing you need a class for every combination you intend to support: compressed, encrypted, compressed-and-encrypted, compressed-and-logged, and so on. Eight classes for three options, sixteen for four. Decorators let you build the combination at runtime by nesting, so three options cost three classes.
Design
The Decorator is both a Component and a holder of a Component. That double relationship is the entire mechanism. Because the Decorator satisfies the same interface it wraps, a Decorator can wrap another Decorator, and the stack can be any depth.
Each decorator's implementation of an operation typically does three things in some order: its own work before delegating, the delegated call to the wrapped object, and its own work after. Whether the added behavior happens before or after the delegation is the difference between validating an input and formatting an output.
wrapped reference points back at the Component
interface rather than at a concrete class, and that is what allows wrappers to
stack indefinitely.An intermediate abstract Decorator class is conventional but not strictly required. Its job is to hold the reference and provide the default pass-through implementation, so each concrete decorator only writes the methods it actually modifies. Without it, every concrete decorator has to forward every method by hand.
Example
Autobot Robotics needed to add safety and diagnostic behavior to arm movement commands, but not to all of them and not in a fixed combination. A customer in a food plant needs every motion logged for audit. A customer running near human operators needs a speed clamp. A customer doing both needs both, and the pair had better compose in either order.
Rather than build a class per customer configuration, the team made the movement command a Component and wrote one decorator per concern: a logging decorator that records the requested motion and then forwards it, a speed-limit decorator that reduces the requested velocity before forwarding, and a dry-run decorator that reports what it would have done and never forwards at all. The installer composes the stack from the site configuration file, and the motion controller downstream is unchanged and unaware.
The dry-run decorator is the one that earned its keep. Being able to wrap a live command chain in something that swallows the final call made it possible to validate a customer's entire program on real hardware without moving the arm.
Comparisons
- Composite shares the same structure almost exactly. A Composite holds many children to build structure; a Decorator holds one child to add behavior. Some authors describe a Decorator as a degenerate Composite.
- Adapter changes an interface. A Decorator deliberately preserves it.
- Proxy also preserves the interface, and the two are easy to confuse. The distinction is intent: a Proxy controls access to its subject; a Decorator adds responsibility to it. A Proxy usually owns or creates its subject; a Decorator is handed one.
- Strategy changes an object's guts rather than its skin. Use Strategy when you can modify the class, Decorator when you cannot.
- Chain of Responsibility is structurally similar, since both have objects forwarding to the next object. But a chain expects exactly one handler to deal with the request, while a decorator stack expects every layer to contribute.
Why It Exists
My take
Decorator exists because subclassing is a compile-time decision and requirements are a runtime problem. Inheritance forces you to name, in advance, every combination of behavior anyone will ever want. That works right up until a customer wants two features you always assumed were mutually exclusive.
What convinces me the pattern is fundamental rather than clever is that the
industry keeps rediscovering it under other names. Unix pipes are decorators
over byte streams. Middleware in a web framework is a decorator stack over a
request handler. Python's @ syntax is named after it outright.
Java's BufferedReader wrapping a FileReader wrapping
a stream is the textbook example shipped as a standard library. When a shape
shows up independently that many times, it is describing something real about
how behavior composes.
The cost is that you trade a class explosion for an object graph you cannot see. Ten decorators is ten small readable classes and one runtime structure that exists nowhere in the source. That is a genuinely worse debugging story, and it is why I think this pattern is often oversold.
Criticisms
Debugging a deep decorator stack is unpleasant. A stack trace becomes a wall
of nearly identical frames, and the object you are inspecting is not the object
the client thinks it has. Identity comparison breaks in a way that surprises
people: a wrapped object is not == to the thing it wraps, so any
code holding a reference to the original is now holding the wrong one.
It also only works cleanly when the interface is narrow. Wrapping an interface with twenty methods means every decorator either forwards twenty methods or inherits a base that does, and a single method added to the interface later touches every decorator in the system.
Finally, the pattern quietly assumes that order does not matter, or that the programmer will get it right. Compress-then-encrypt and encrypt-then-compress produce very different results, and nothing in the type system will warn you.