Intuition
Imagine a basic coffee:
Start with plain coffee.
Optionally add milk.
Optionally add sugar.
Optionally add whipped cream.
You don’t want separate classes for every combination like MilkSugarCoffee, WhippedMilkCoffee, etc.
Instead, you take a base coffee object and wrap it in small objects, each adding one feature.
That is the intuition behind the Decorator Design Pattern: wrap an object to extend its behavior without touching the original class.
Intent of the Decorator pattern
The core intent:
Attach additional responsibilities to an object dynamically, using wrappers instead of subclasses.Key ideas:
There is a component interface that both the core object and decorators implement.
A concrete component provides the basic behavior.
A decorator holds a reference to a component and delegates to it, adding behavior before or after the delegation.
Multiple decorators can be stacked, building complex behavior from simple layers.
You get an extension by composition, not by deep inheritance trees.
Basic structure
Typical roles:
Component
Interface or abstract base class for all objects that can be decorated.
ConcreteComponent
Basic implementation of the component.
Base Decorator
Implements
Componentand stores a reference/pointer to anotherComponent.Forwards calls to the wrapped component.
Concrete Decorators
Derive from the base decorator.
Override methods to add extra work around the delegated call.
This is often called a “wrapper” because the decorator wraps the original object.
C++ example: Coffee with add‑ons
Step 1: Component interface
Step 2: Concrete component
Step 3: Base decorator
The base decorator implements Beverage and holds a pointer to another Beverage.
Concrete decorators will override the methods and delegate to beverage.
Step 4: Concrete decorators (Milk, Sugar)
Each decorator:
Calls the wrapped object’s method (
beverage->description(),beverage->cost()).Adds its own behavior (extra text and extra cost).
Step 5: Using decorators (stacking)
Flow:
Start with
PlainCoffee.Wrap it with
MilkDecorator.Wrap the result with
SugarDecorator.The final object is still a
Beverage*, but behavior = “plain coffee + milk + sugar”.
Why not just use inheritance?
Using only inheritance, you might end up with:
PlainCoffeeMilkCoffeeSugarCoffeeMilkSugarCoffeeWhippedMilkSugarCoffeeand so on.
Problems:
Class explosion: every new feature multiplies combinations.
Features are hard‑coded; changing combinations requires new subclasses.
Behavior is fixed at compile time.
The decorator improves this by:
Moving each extra feature into its own decorator class.
Allowing features to be composed at runtime by stacking decorators.
Keeping the base component simple and stable (good for the Open‑Closed Principle).
Real‑world analogies and uses
Common places where Decorator appears:
I/O streams: a base stream wrapped with buffering, filtering, compression, etc.
GUI components: adding scrollbars, borders, and shadows to a basic window by wrapping it.
Cross‑cutting concerns: logging, caching, authentication added around service calls by wrapping the original service.
These all follow the same pattern: keep the core simple, and wrap it when you want extra behavior.
When to use Decorator
A decorator is a good choice when:
You want to add responsibilities to objects dynamically, not just via fixed subclasses.
You want to avoid a huge subclass hierarchy of all feature combinations.
Different contexts need different combinations of features on the same base type.
It may be overkill when:
You don’t need runtime flexibility; one or two simple subclasses would do.
Too many nested decorators would make debugging and reasoning about behavior difficult.
A useful test: whenever you think “start with X and optionally add A, B, C features in various combinations,” Decorator is worth considering.