Design Patterns · Structural Patterns
Decorator
Wrap an object with additional behavior while keeping the same contract. Stack responsibilities and make their order explicit.
This lesson follows Composite. Composite groups children. Decorator adds a layer around one component.
Decorator attaches behavior to an object through wrappers that implement the same interface. A wrapper can itself be wrapped. The flexibility costs another layer of delegation and rules about ordering.
Avoid a subclass for every feature combination
A text formatter can add a prefix, uppercase text, or do both. A subclass for each combination multiplies as features arrive. Independent decorators let a client choose only the needed layers.
interface TextTransform { apply(text: string): string }
class Identity implements TextTransform {
apply(text: string) { return text; }
}
class TransformDecorator implements TextTransform {
constructor(protected inner: TextTransform) {}
apply(text: string) { return this.inner.apply(text); }
}
class Prefix extends TransformDecorator {
apply(text: string) { return "note: " + super.apply(text); }
}
class Uppercase extends TransformDecorator {
apply(text: string) { return super.apply(text).toUpperCase(); }
}
const first = new Uppercase(new Prefix(new Identity()));
first.apply("hello"); // NOTE: HELLO
const second = new Prefix(new Uppercase(new Identity()));
second.apply("hello"); // note: HELLO
TextTransform is the component contract. Identity provides the base behavior. TransformDecorator stores a component, including another decorator. Prefix and Uppercase add one responsibility each.
Observe the order
Try it
Swap the outer layer
Identity returns hello
Prefix returns note: hello
Uppercase returns NOTE: HELLO
Identity returns hello
Uppercase returns HELLO
Prefix returns note: HELLO
The interface is identical. The composed behavior is different.
Step through
Follow the return path
The outer wrapper delegates
Uppercase calls its inner transform before uppercasing anything. Prefix delegates too. The call reaches Identity.
Each wrapper adds its responsibility
hello → note: hello → NOTE: HELLO
A decorator can also act before delegation, such as measuring a start time, then act again after the result returns.
The client still uses one contract
const transform: TextTransform = first
transform.apply('hello') → NOTE: HELLO
The client does not need to know the wrapper count. Code that depends on a concrete component's extra methods loses that transparency.
Keep the contract meaningful
An interchangeable signature needs an interchangeable promise
A decorator must still honor the component's documented behavior. A logging wrapper that swallows a failure changes the contract if callers expect that failure. Specify any new behavior clients must understand.
Optional compression and encryption are a familiar use. Their order changes the stored representation, and reading must reverse the writing transformations. Removing an inner decorator often means rebuilding the stack. There is no free undo button for structure.
Check yourself
Can a decorator with the right signature ignore the base contract's error behavior?
Not quite. A compiler cannot prove every behavioral guarantee.
Correct. Interchangeability includes results and failures, not only method names.
Map the roles
| Role | In this example | Job |
|---|---|---|
| Component | TextTransform | Declares the interface shared by wrappers and the objects they wrap |
| Concrete component | Identity | The object being wrapped. Defines the basic behavior that decorators can change. |
| Base decorator | TransformDecorator | Holds a reference typed as the component interface, so it can wrap a concrete component or another decorator. Delegates every call to it. |
| Concrete decorators | Prefix, Uppercase | Override methods and add behavior before or after calling the parent, which delegates inward |
| Client | the code that builds new Uppercase(new Prefix(new Identity())) | Wraps components in as many layers as it needs and uses them through the component interface |
Check yourself
Why is the base decorator's field typed as TextTransform rather than Identity?
Not quite. Delegation is the point. The field type decides what can be wrapped.
Correct. Typing the field as the concrete component would allow only one layer.
Reach for it when objects need optional behavior at runtime
- you need to add extra behavior to objects at runtime without breaking the code that uses them. Decorator organizes business logic into layers. Each layer is a decorator, and the client combines them into an object at runtime. All of them share an interface, so the code that uses the result does not change.
- extending behavior with inheritance is awkward or impossible. Many languages let a class be marked
finalso nobody can subclass it. Wrapping is then the only way to reuse and extend its behavior.
Check yourself
A vendor's HttpClient class is final, and you need retry behavior around it. Which use of Decorator applies?
Correct. A final class cannot be subclassed, but it can be wrapped.
Not quite. You cannot edit a vendor's class.
Implement it in seven steps
Refactor existing code toward the pattern in this order:
- Make sure your business domain can be represented as a primary component with multiple optional layers on top of it.
- Find the methods common to the primary component and the optional layers. Declare them in a component interface.
- Create a concrete component class and define the base behavior in it.
- Create a base decorator class with a field that references a wrapped object. Type the field with the component interface so it can hold concrete components and decorators. The base decorator delegates all work to the wrapped object.
- Make sure every class implements the component interface.
- Create concrete decorators by extending the base decorator. Each one runs its own behavior before or after calling the parent method, which always delegates to the wrapped object.
- Let client code create the decorators and compose them in the order it needs.
Check yourself
Step 6 says a concrete decorator calls the parent method. What does that call do?
Not quite. The base decorator only delegates. It adds no behavior of its own.
Correct. Skipping that call would cut off every inner layer and the component.
Name what it costs
| You gain | You pay |
|---|---|
| Extend an object's behavior without a new subclass | Removing one specific wrapper from the middle of a stack is hard |
| Add or remove responsibilities at runtime | A decorator whose behavior does not depend on its place in the stack is hard to write |
| Combine several behaviors by wrapping an object in several decorators | The code that builds the layers can look ugly |
| Split a monolithic class into small classes, one per behavior (single responsibility) | Code that depends on a concrete component's extra methods loses that access once wrapped |
Check yourself
Compression and encryption decorators wrap a file writer. Why does their order matter?
Correct. Order sensitivity is a real cost of the pattern. Document it.
Not quite. Most decorators do not commute. That is why order needs care.
Do not confuse it with its neighbors
| Pattern | How it relates |
|---|---|
| Adapter | Adapter changes an existing object's interface. Decorator enhances an object without changing its interface, and it supports recursive stacking, which Adapter does not. |
| Proxy | Both delegate through the same interface. A proxy usually manages its service object's life cycle itself. Decorators are always composed by the client. |
| Chain of Responsibility | Both pass a request through a series of objects. Chain handlers act independently and can stop the request at any point. A decorator extends behavior and cannot stop the flow without breaking its contract. |
| Composite | Similar recursive structure. A decorator has one child and adds responsibilities. A composite sums up the results of many children. A decorator can extend one object in a composite tree. |
| Strategy | Decorator changes an object's skin. Strategy changes its guts. |
| Prototype | Designs that stack many decorators can clone a finished stack instead of rebuilding it. |
Check yourself
Each object in a series may answer the request and stop it, or pass it on. Is that Decorator?
Correct. Stopping the flow is the defining difference between the two.
Not quite. The structures look alike. The intent and the stopping rule differ.
Retrieve and apply
Check yourself
A logging decorator catches every error and returns an empty string. The base contract promises to propagate failures. What is wrong?
Correct. The same method signature does not make the behavior substitutable. Log and rethrow unless the contract explicitly permits a fallback.
Not quite. Before and after behavior is central to the pattern. The problem is swallowing a promised failure.
Build two wrapper orders for a formatter and predict both outputs before clicking. Continue to Facade to simplify a subsystem behind one focused entry point.
Source: Alexander Shvets, Dive Into Design Patterns (深入设计模式), Chinese edition v2021-1.25. Decorator, printed pages 181–197. Explanations, examples, and exercises are adapted for this course.