Design Patterns Decorator
The Decorator pattern adds new behavior to an object at runtime without changing the object's class. Think of it like a coffee order — you start with a plain espresso and then add milk, sugar, and caramel on top. Each addition wraps around the previous cup and enhances it. The espresso itself never changes.
Decorator belongs to the Structural pattern family. It gives you a flexible alternative to subclassing when you need to extend object behavior.
The Coffee Shop Analogy
A basic espresso costs $2.00. A customer adds milk (+$0.50), then caramel (+$0.75), then whipped cream (+$0.60). Each "add-on" wraps around the previous order and contributes its price and description. The base espresso object stays simple and unchanged.
Base Object: Espresso → cost: $2.00, desc: "Espresso"
Wrap with: MilkDecorator → cost: +$0.50
Wrap with: CaramelDecorator → cost: +$0.75
Wrap with: WhipDecorator → cost: +$0.60
Final cost: $2.00 + $0.50 + $0.75 + $0.60 = $3.85
Final desc: "Espresso, Milk, Caramel, Whip"
Visual Diagram: Decorator Layering
+----------------------------+ | WhipDecorator | ← outermost wrapper | +------------------------+| | | CaramelDecorator || ← second wrapper | | +--------------------+|| | | | MilkDecorator ||| ← first wrapper | | | +--------------+ ||| | | | | Espresso | ||| ← original object | | | | cost: $2.00 | ||| | | | +--------------+ ||| | | +--------------------+|| | +------------------------+| +----------------------------+ Each layer calls the inner layer's cost() and adds its own.
Key Participants
Component Interface
Defines the operations that both the original object and all decorators must support. This is the contract that keeps everything interchangeable.
Concrete Component
The base object — the thing you want to decorate. It provides the default behavior that decorators build on.
Base Decorator
Holds a reference to a Component and delegates all operations to it. All concrete decorators extend this class. The reference is the key — it allows decorators to chain.
Concrete Decorator
Extends the Base Decorator, adds its own behavior before or after delegating to the wrapped object.
Structure Diagram
«interface»
Component
+------------------+
| + operation() |
+------------------+
▲ ▲
| |
ConcreteComponent BaseDecorator
+------------+ +------------------+
| operation()| | component: Comp |
+------------+ | + operation() |
+------------------+
▲
|
ConcreteDecoratorA ConcreteDecoratorB
+---------------+ +---------------+
| extra state | | + operation() |
| + operation() | +---------------+
+---------------+
Code Walkthrough in Python
Step 1 — Define the Component Interface
class Coffee:
def cost(self):
raise NotImplementedError
def description(self):
raise NotImplementedError
Step 2 — Create the Concrete Component
class Espresso(Coffee):
def cost(self):
return 2.00
def description(self):
return "Espresso"
Step 3 — Build the Base Decorator
class CoffeeDecorator(Coffee):
def __init__(self, coffee):
self._coffee = coffee
def cost(self):
return self._coffee.cost()
def description(self):
return self._coffee.description()
Step 4 — Create Concrete Decorators
class Milk(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.50
def description(self):
return self._coffee.description() + ", Milk"
class Caramel(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.75
def description(self):
return self._coffee.description() + ", Caramel"
class WhippedCream(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.60
def description(self):
return self._coffee.description() + ", Whip"
Step 5 — Assemble the Order
drink = Espresso()
drink = Milk(drink)
drink = Caramel(drink)
drink = WhippedCream(drink)
print(drink.description()) # Espresso, Milk, Caramel, Whip
print(f"${drink.cost():.2f}") # $3.85
How the Call Chain Flows
WhippedCream.cost()
calls → Caramel.cost()
calls → Milk.cost()
calls → Espresso.cost() → returns 2.00
Milk returns 2.00 + 0.50 = 2.50
Caramel returns 2.50 + 0.75 = 3.25
WhippedCream returns 3.25 + 0.60 = 3.85
Each decorator adds its value on top of whatever the inner object returns. The chain resolves from the inside out.
Decorator vs Subclassing
Suppose you need 4 coffee types: Espresso, Latte, Cappuccino, Mocha — and 3 add-ons: Milk, Caramel, Whip. Without Decorator, subclassing every combination produces:
SUBCLASSING APPROACH (class explosion): EspressoWithMilk EspressoWithMilkAndCaramel EspressoWithMilkAndCaramelAndWhip LatteWithMilk LatteWithCaramel ... (4 bases × 2³ combinations = 32 classes) DECORATOR APPROACH: 4 base classes + 3 decorator classes = 7 classes total All 32 combinations built at runtime by stacking decorators.
Real-World Uses of Decorator
USE CASE WHAT GETS DECORATED
-----------------------------------------------------
Python's @functools.lru_cache Adds caching to any function
Java's InputStream wrappers Adds buffering, compression,
BufferedInputStream encryption to raw streams
GZipInputStream
UI component borders/padding Adds visual wrappers around
ScrollDecorator base widgets in GUI toolkits
BorderDecorator
Logging middleware Wraps API calls to log
request/response data
Authentication middleware Wraps request handlers to
check tokens before executing
Decorator in Python: The @decorator Syntax
Python uses the Decorator pattern natively with the @ symbol on functions.
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"Done {func.__name__}")
return result
return wrapper
@log_calls
def process_payment(amount):
print(f"Processing ${amount}")
process_payment(50)
# Output:
# Calling process_payment
# Processing $50
# Done process_payment
log_calls wraps process_payment without touching the original function — pure Decorator pattern.
When to Use the Decorator Pattern
Use Decorator When:
- You want to add responsibilities to objects without altering their class.
- Extension by subclassing would produce too many classes.
- You need to add or remove behaviors dynamically at runtime.
- Several independent extensions should be combinable in any order.
Avoid Decorator When:
- The component interface is large — every decorator must implement all methods, even ones it does not change.
- Order of decorators matters and can cause bugs — document wrapper order clearly.
Advantages and Disadvantages
Advantages
- Extends behavior without modifying existing classes — follows the Open/Closed Principle.
- Mixes and matches responsibilities at runtime.
- Avoids class explosion caused by every subclass combination.
Disadvantages
- Many small wrapper objects can make debugging harder — the call stack grows deep.
- A decorator is not the same type as the concrete component at runtime — identity checks like
isinstance(obj, Espresso)fail on decorated objects.
Quick Summary
DECORATOR PATTERN — AT A GLANCE
Problem: Adding features via subclassing creates too many
classes; you need to add behavior dynamically.
Solution: Wrap objects in decorator classes that add behavior
before/after delegating to the wrapped object.
Key idea: Decorators share the same interface as the object
they wrap — clients treat them identically.
Best for: I/O streams, middleware stacks, UI components,
logging, caching, authentication layers.
The Decorator pattern gives objects new powers at runtime without rewriting a single line of the original class. Layer decorators freely, combine them in any order, and remove them just as easily.
