Decorator
Attach additional responsibilities to an object dynamically, providing a flexible alternative to subclassing for extending functionality.
Intent
Wrap an object inside another object that adds new behavior before or after delegating to the original, allowing responsibilities to be composed at runtime rather than baked into a rigid class hierarchy.
Problem
You need to add optional behaviors -- such as logging, encryption, compression, or caching -- to individual objects at runtime. Using inheritance to create every possible combination would lead to a class explosion (e.g., LoggingEncryptedCompressedStream), and modifying the original class violates the Open/Closed Principle.
Solution
Define a common interface for both the core component and its decorators. Each decorator implements the interface and holds a reference to a wrapped component. It delegates the core work to the wrapped object and adds its own behavior before or after the delegation. Decorators can be stacked arbitrarily, composing behaviors at runtime.
Participants
- Component -- the common interface for both concrete objects and decorators
- ConcreteComponent -- the original object that can be decorated
- Decorator -- base class or interface that wraps a Component and conforms to its interface
- ConcreteDecorator -- adds specific behavior before or after delegating to the wrapped Component
Advantages
- More flexible than static inheritance -- behaviors can be added and removed at runtime
- Avoids feature-laden classes high up in the hierarchy (class explosion problem)
- Follows the Single Responsibility Principle -- each decorator handles one concern
- Decorators can be composed in any order to create custom combinations
Disadvantages
- Many small objects can make the system harder to understand and debug
- Removing a specific decorator from the middle of a chain is difficult
- Decorator ordering can matter, and incorrect ordering leads to subtle bugs
- The component identity changes -- obj !== decoratedObj, which can break equality checks
Real-World Analogy
Wearing layers of clothing is the Decorator pattern. You start with a base layer (t-shirt), then add a sweater for warmth, then a rain jacket for waterproofing. Each layer adds a responsibility without altering the layers beneath, and you can mix and match depending on conditions.
Use Cases
- Adding logging, timing, or retry logic to service calls without modifying the service
- Composing I/O stream behaviors: buffering, compression, encryption
- Dynamically adding validation rules to form fields in a UI framework
- Wrapping a data repository with caching or audit-trail functionality
- Adding authentication or rate-limiting middleware to HTTP handlers
Code Examples
Decorators that add logging and encryption to a DataSource interface, demonstrating how behaviors compose by wrapping one decorator inside another.
Related Patterns
Adapter
Convert the interface of a class into another interface clients expect, enabling classes to work together that otherwise could not due to incompatible interfaces.