
Decorator
Lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.
classDiagram
class Component {
<<interface>>
+operation()
}
class ConcreteComponent {
+operation()
}
class BaseDecorator {
-Component wrappee
+operation()
}
class ConcreteDecoratorA {
+operation()
}
class ConcreteDecoratorB {
+operation()
}
Component <|.. ConcreteComponent
Component <|.. BaseDecorator
BaseDecorator o-- Component
BaseDecorator <|-- ConcreteDecoratorA
BaseDecorator <|-- ConcreteDecoratorB
When to use
Use the Decorator pattern when you need to be able to assign extra behaviors to objects at runtime without breaking the code that uses these objects. Use when it’s awkward or not possible to extend an object’s behavior using inheritance.
Explanation
Problem
Imagine that you’re working on a notification library which lets other programs notify their users about important events. The initial version of the library was based on the Notifier class that had only a few fields, a constructor and a single send method. The method could accept a message argument and send it to a list of emails passed to the constructor.
Later, users wanted SMS notifications, then Facebook, then Slack. And they wanted to combine them. “Email + SMS”, “SMS + Slack”, “Email + SMS + Slack”. You can’t easy support all combinations with inheritance (Combinatorial Explosion).
Solution
Inheritance is static. Composition is dynamic. With composition, one object has a reference to another and delegates it some work.
The Decorator pattern relies on composition. You have a Notifier interface. The basic Notifier sends emails. Decorators (SMSDecorator, SlackDecorator) implement Notifier, but they also contain a reference to another Notifier. They call the wrapped notifier’s send method, and then execute their own behavior.
Real world problem
- Clothing: You wear a Shirt. It’s cold, you put on a Sweater (decorate Shirt). Still cold, you put on a Jacket (decorate Sweater). If it rains, you put on a Raincoat. You can strip them off strictly in reverse order.
- Streams: Java I/O Streams.
new BufferedReader(new FileReader(file)). - Middlewares: Web frameworks use middleware to decorate request handling (Auth, Logging, Compression).
Pros and Cons
| Pros | Cons |
|---|---|
| - Extensibility: You can extend an object’s behavior without making a new subclass. - Dynamic: You can add or remove responsibilities from an object at runtime. - Combinations: You can combine several behaviors by wrapping an object into multiple decorators. - Single Responsibility Principle: You can divide a monolithic class that implements many possible variants of behavior into several smaller classes. |
- Removal: It’s hard to remove a specific wrapper from the wrappers stack. - Order: It’s hard to implement a decorator in such a way that its behavior doesn’t depend on the order in the decorators stack. - Complexity: The initial configuration code of layers might look ugly. |
Comparison
- Adapter: Adapter provides a different interface to the wrapped object. Proxy provides it with the same interface. Decorator provides it with an enhanced interface (dynamically).
- Chain of Responsibility: Decorator allows you to handle a request, pass it to the next decorator (wrapper). Chain of Responsibility allows you to pass a request along a dynamic chain of potential handlers.
Code example
Typescript
Php