Decorator Pattern
Category: Structural Pattern
Overview
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality, allowing behavior to be added to individual objects without affecting other objects of the same class.
Usage Guidelines
Use when:
- Need to add responsibilities to objects at runtime
- Features should be added and removed dynamically
- Want to combine features in various ways
- Existing classes should remain unchanged (Open/Closed Principle)
Avoid when:
- All behavior is known at compile time and doesn't change
- A single subclass would suffice for simple enhancements
- The order of applying decorators significantly affects behavior and causes confusion
- Multiple wrapper layers introduce unacceptable performance overhead
Implementation
from abc import ABC, abstractmethod
# Component Interface
class Coffee(ABC):
@abstractmethod
def get_cost(self) -> float:
pass
@abstractmethod
def get_description(self) -> str:
pass
# Concrete Component
class SimpleCoffee(Coffee):
def get_cost(self) -> float:
return 1.0
def get_description(self) -> str:
return "Simple Coffee"
# Base Decorator
class CoffeeDecorator(Coffee):
def __init__(self, coffee: Coffee):
self._coffee = coffee
def get_cost(self) -> float:
return self._coffee.get_cost()
def get_description(self) -> str:
return self._coffee.get_description()
# Concrete Decorators
class MilkDecorator(CoffeeDecorator):
def get_cost(self) -> float:
return self._coffee.get_cost() + 0.5
def get_description(self) -> str:
return self._coffee.get_description() + ", Milk"
class SugarDecorator(CoffeeDecorator):
def get_cost(self) -> float:
return self._coffee.get_cost() + 0.2
def get_description(self) -> str:
return self._coffee.get_description() + ", Sugar"
class VanillaDecorator(CoffeeDecorator):
def get_cost(self) -> float:
return self._coffee.get_cost() + 0.7
def get_description(self) -> str:
return self._coffee.get_description() + ", Vanilla"
Usage
# Start with simple coffee
coffee = SimpleCoffee()
print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
# Output: Simple Coffee: $1.00
# Add milk
coffee = MilkDecorator(coffee)
print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
# Output: Simple Coffee, Milk: $1.50
# Add sugar
coffee = SugarDecorator(coffee)
print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
# Output: Simple Coffee, Milk, Sugar: $1.70
# Add vanilla
coffee = VanillaDecorator(coffee)
print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
# Output: Simple Coffee, Milk, Sugar, Vanilla: $2.40
Trade-offs
Benefits:
- Add or remove responsibilities at runtime for flexibility
- Extend functionality without modifying existing code (Open/Closed Principle)
- Each decorator focuses on one concern (Single Responsibility)
- Mix and match decorators for different combinations
Drawbacks:
- Many small objects and layers can be hard to understand
- Decorator order may matter, causing confusion
- Decorated objects differ from original objects causing identity issues
- Stack traces show multiple wrapper layers making debugging difficult
Real-World Examples
- I/O streams with BufferedInputStream, GZIPInputStream wrapping basic streams
- GUI components adding scrollbars, borders to UI elements
- Middleware adding logging, authentication, caching to HTTP handlers
- Caching layers adding caching to database or API calls
- Adapter
- Composite
- Proxy
- Strategy
- Chain of Responsibility
API Reference
design_patterns.structural.decorator
Decorator Pattern Module
The Decorator pattern attaches additional responsibilities to an object dynamically.
Decorators provide a flexible alternative to subclassing for extending functionality.
This pattern allows behavior to be added to individual objects without affecting other
objects of the same class.
Example
Decorating a coffee order:
coffee = SimpleCoffee()
coffee = MilkDecorator(coffee)
coffee = SugarDecorator(coffee)
print(coffee.get_description()) # "Simple Coffee, Milk, Sugar"
print(coffee.get_cost()) # 3.0
Coffee
Bases: ABC
Abstract base class for coffee.
Source code in src/design_patterns/structural/decorator.py
| class Coffee(ABC):
"""Abstract base class for coffee."""
@abstractmethod
def get_cost(self) -> float:
"""Get the cost of the coffee.
Returns:
The cost in dollars.
"""
pass
@abstractmethod
def get_description(self) -> str:
"""Get the description of the coffee.
Returns:
Coffee description.
"""
pass
|
get_cost()
abstractmethod
Get the cost of the coffee.
Returns:
Source code in src/design_patterns/structural/decorator.py
| @abstractmethod
def get_cost(self) -> float:
"""Get the cost of the coffee.
Returns:
The cost in dollars.
"""
pass
|
get_description()
abstractmethod
Get the description of the coffee.
Returns:
Source code in src/design_patterns/structural/decorator.py
| @abstractmethod
def get_description(self) -> str:
"""Get the description of the coffee.
Returns:
Coffee description.
"""
pass
|
CoffeeDecorator
Bases: Coffee
Base decorator class for coffee add-ons.
Source code in src/design_patterns/structural/decorator.py
| class CoffeeDecorator(Coffee):
"""Base decorator class for coffee add-ons."""
def __init__(self, coffee: Coffee) -> None:
"""Initialize decorator with a coffee instance.
Args:
coffee: The coffee to decorate.
"""
self._coffee = coffee
def get_cost(self) -> float:
"""Get cost including this decorator.
Returns:
Total cost.
"""
return self._coffee.get_cost()
def get_description(self) -> str:
"""Get description including this decorator.
Returns:
Complete description.
"""
return self._coffee.get_description()
|
__init__(coffee)
Initialize decorator with a coffee instance.
Parameters:
| Name |
Type |
Description |
Default |
coffee
|
Coffee
|
|
required
|
Source code in src/design_patterns/structural/decorator.py
| def __init__(self, coffee: Coffee) -> None:
"""Initialize decorator with a coffee instance.
Args:
coffee: The coffee to decorate.
"""
self._coffee = coffee
|
get_cost()
Get cost including this decorator.
Returns:
Source code in src/design_patterns/structural/decorator.py
| def get_cost(self) -> float:
"""Get cost including this decorator.
Returns:
Total cost.
"""
return self._coffee.get_cost()
|
get_description()
Get description including this decorator.
Returns:
Source code in src/design_patterns/structural/decorator.py
| def get_description(self) -> str:
"""Get description including this decorator.
Returns:
Complete description.
"""
return self._coffee.get_description()
|
CompressionDecorator
Bases: DataSourceDecorator
Decorator that adds compression to data operations.
Source code in src/design_patterns/structural/decorator.py
| class CompressionDecorator(DataSourceDecorator):
"""Decorator that adds compression to data operations."""
def write_data(self, data: str) -> None:
"""Write compressed data.
Args:
data: Data to compress and write.
"""
compressed = self._compress(data)
self._source.write_data(compressed)
def read_data(self) -> str:
"""Read and decompress data.
Returns:
Decompressed data.
"""
compressed = self._source.read_data()
return self._decompress(compressed)
def _compress(self, data: str) -> str:
"""Simple compression simulation (prefix with marker).
Args:
data: Data to compress.
Returns:
Compressed data.
"""
return f"[COMPRESSED]{data}"
def _decompress(self, data: str) -> str:
"""Simple decompression simulation (remove prefix).
Args:
data: Data to decompress.
Returns:
Decompressed data.
"""
if data.startswith("[COMPRESSED]"):
return data[12:]
return data
|
read_data()
Read and decompress data.
Returns:
Source code in src/design_patterns/structural/decorator.py
| def read_data(self) -> str:
"""Read and decompress data.
Returns:
Decompressed data.
"""
compressed = self._source.read_data()
return self._decompress(compressed)
|
write_data(data)
Write compressed data.
Parameters:
| Name |
Type |
Description |
Default |
data
|
str
|
Data to compress and write.
|
required
|
Source code in src/design_patterns/structural/decorator.py
| def write_data(self, data: str) -> None:
"""Write compressed data.
Args:
data: Data to compress and write.
"""
compressed = self._compress(data)
self._source.write_data(compressed)
|
DataSource
Bases: ABC
Abstract interface for data sources.
Source code in src/design_patterns/structural/decorator.py
| class DataSource(ABC):
"""Abstract interface for data sources."""
@abstractmethod
def write_data(self, data: str) -> None:
"""Write data to the source.
Args:
data: The data to write.
"""
pass
@abstractmethod
def read_data(self) -> str:
"""Read data from the source.
Returns:
The data.
"""
pass
|
read_data()
abstractmethod
Read data from the source.
Returns:
Source code in src/design_patterns/structural/decorator.py
| @abstractmethod
def read_data(self) -> str:
"""Read data from the source.
Returns:
The data.
"""
pass
|
write_data(data)
abstractmethod
Write data to the source.
Parameters:
| Name |
Type |
Description |
Default |
data
|
str
|
|
required
|
Source code in src/design_patterns/structural/decorator.py
| @abstractmethod
def write_data(self, data: str) -> None:
"""Write data to the source.
Args:
data: The data to write.
"""
pass
|
DataSourceDecorator
Bases: DataSource
Base decorator for data sources.
Source code in src/design_patterns/structural/decorator.py
| class DataSourceDecorator(DataSource):
"""Base decorator for data sources."""
def __init__(self, source: DataSource) -> None:
"""Initialize decorator.
Args:
source: The data source to decorate.
"""
self._source = source
def write_data(self, data: str) -> None:
"""Write data through decorator.
Args:
data: Data to write.
"""
self._source.write_data(data)
def read_data(self) -> str:
"""Read data through decorator.
Returns:
The data.
"""
return self._source.read_data()
|
__init__(source)
Initialize decorator.
Parameters:
| Name |
Type |
Description |
Default |
source
|
DataSource
|
The data source to decorate.
|
required
|
Source code in src/design_patterns/structural/decorator.py
| def __init__(self, source: DataSource) -> None:
"""Initialize decorator.
Args:
source: The data source to decorate.
"""
self._source = source
|
read_data()
Read data through decorator.
Returns:
Source code in src/design_patterns/structural/decorator.py
| def read_data(self) -> str:
"""Read data through decorator.
Returns:
The data.
"""
return self._source.read_data()
|
write_data(data)
Write data through decorator.
Parameters:
| Name |
Type |
Description |
Default |
data
|
str
|
|
required
|
Source code in src/design_patterns/structural/decorator.py
| def write_data(self, data: str) -> None:
"""Write data through decorator.
Args:
data: Data to write.
"""
self._source.write_data(data)
|
EncryptionDecorator
Bases: DataSourceDecorator
Decorator that adds encryption to data operations.
Source code in src/design_patterns/structural/decorator.py
| class EncryptionDecorator(DataSourceDecorator):
"""Decorator that adds encryption to data operations."""
def write_data(self, data: str) -> None:
"""Write encrypted data.
Args:
data: Data to encrypt and write.
"""
encrypted = self._encrypt(data)
self._source.write_data(encrypted)
def read_data(self) -> str:
"""Read and decrypt data.
Returns:
Decrypted data.
"""
encrypted = self._source.read_data()
return self._decrypt(encrypted)
def _encrypt(self, data: str) -> str:
"""Simple encryption (reversed string).
Args:
data: Data to encrypt.
Returns:
Encrypted data.
"""
return data[::-1]
def _decrypt(self, data: str) -> str:
"""Simple decryption (reverse the reversed string).
Args:
data: Data to decrypt.
Returns:
Decrypted data.
"""
return data[::-1]
|
read_data()
Read and decrypt data.
Returns:
Source code in src/design_patterns/structural/decorator.py
| def read_data(self) -> str:
"""Read and decrypt data.
Returns:
Decrypted data.
"""
encrypted = self._source.read_data()
return self._decrypt(encrypted)
|
write_data(data)
Write encrypted data.
Parameters:
| Name |
Type |
Description |
Default |
data
|
str
|
Data to encrypt and write.
|
required
|
Source code in src/design_patterns/structural/decorator.py
| def write_data(self, data: str) -> None:
"""Write encrypted data.
Args:
data: Data to encrypt and write.
"""
encrypted = self._encrypt(data)
self._source.write_data(encrypted)
|
FileDataSource
Bases: DataSource
Basic file data source.
Source code in src/design_patterns/structural/decorator.py
| class FileDataSource(DataSource):
"""Basic file data source."""
def __init__(self, filename: str) -> None:
"""Initialize file data source.
Args:
filename: The file name.
"""
self.filename = filename
self._data: str = ""
def write_data(self, data: str) -> None:
"""Write data to file.
Args:
data: Data to write.
"""
self._data = data
def read_data(self) -> str:
"""Read data from file.
Returns:
The stored data.
"""
return self._data
|
__init__(filename)
Initialize file data source.
Parameters:
| Name |
Type |
Description |
Default |
filename
|
str
|
|
required
|
Source code in src/design_patterns/structural/decorator.py
| def __init__(self, filename: str) -> None:
"""Initialize file data source.
Args:
filename: The file name.
"""
self.filename = filename
self._data: str = ""
|
read_data()
Read data from file.
Returns:
Source code in src/design_patterns/structural/decorator.py
| def read_data(self) -> str:
"""Read data from file.
Returns:
The stored data.
"""
return self._data
|
write_data(data)
Write data to file.
Parameters:
| Name |
Type |
Description |
Default |
data
|
str
|
|
required
|
Source code in src/design_patterns/structural/decorator.py
| def write_data(self, data: str) -> None:
"""Write data to file.
Args:
data: Data to write.
"""
self._data = data
|
MilkDecorator
Bases: CoffeeDecorator
Decorator that adds milk to coffee.
Source code in src/design_patterns/structural/decorator.py
| class MilkDecorator(CoffeeDecorator):
"""Decorator that adds milk to coffee."""
def get_cost(self) -> float:
"""Get cost with milk added.
Returns:
Cost plus 0.5 for milk.
"""
return self._coffee.get_cost() + 0.5
def get_description(self) -> str:
"""Get description with milk.
Returns:
Description with milk added.
"""
return self._coffee.get_description() + ", Milk"
|
get_cost()
Get cost with milk added.
Returns:
Source code in src/design_patterns/structural/decorator.py
| def get_cost(self) -> float:
"""Get cost with milk added.
Returns:
Cost plus 0.5 for milk.
"""
return self._coffee.get_cost() + 0.5
|
get_description()
Get description with milk.
Returns:
| Type |
Description |
str
|
Description with milk added.
|
Source code in src/design_patterns/structural/decorator.py
| def get_description(self) -> str:
"""Get description with milk.
Returns:
Description with milk added.
"""
return self._coffee.get_description() + ", Milk"
|
SimpleCoffee
Bases: Coffee
Basic coffee without any additions.
Source code in src/design_patterns/structural/decorator.py
| class SimpleCoffee(Coffee):
"""Basic coffee without any additions."""
def get_cost(self) -> float:
"""Get base coffee cost.
Returns:
Base cost of 1.0.
"""
return 1.0
def get_description(self) -> str:
"""Get base coffee description.
Returns:
Description string.
"""
return "Simple Coffee"
|
get_cost()
Get base coffee cost.
Returns:
Source code in src/design_patterns/structural/decorator.py
| def get_cost(self) -> float:
"""Get base coffee cost.
Returns:
Base cost of 1.0.
"""
return 1.0
|
get_description()
Get base coffee description.
Returns:
Source code in src/design_patterns/structural/decorator.py
| def get_description(self) -> str:
"""Get base coffee description.
Returns:
Description string.
"""
return "Simple Coffee"
|
SugarDecorator
Bases: CoffeeDecorator
Decorator that adds sugar to coffee.
Source code in src/design_patterns/structural/decorator.py
| class SugarDecorator(CoffeeDecorator):
"""Decorator that adds sugar to coffee."""
def get_cost(self) -> float:
"""Get cost with sugar added.
Returns:
Cost plus 0.2 for sugar.
"""
return self._coffee.get_cost() + 0.2
def get_description(self) -> str:
"""Get description with sugar.
Returns:
Description with sugar added.
"""
return self._coffee.get_description() + ", Sugar"
|
get_cost()
Get cost with sugar added.
Returns:
Source code in src/design_patterns/structural/decorator.py
| def get_cost(self) -> float:
"""Get cost with sugar added.
Returns:
Cost plus 0.2 for sugar.
"""
return self._coffee.get_cost() + 0.2
|
get_description()
Get description with sugar.
Returns:
| Type |
Description |
str
|
Description with sugar added.
|
Source code in src/design_patterns/structural/decorator.py
| def get_description(self) -> str:
"""Get description with sugar.
Returns:
Description with sugar added.
"""
return self._coffee.get_description() + ", Sugar"
|
VanillaDecorator
Bases: CoffeeDecorator
Decorator that adds vanilla to coffee.
Source code in src/design_patterns/structural/decorator.py
| class VanillaDecorator(CoffeeDecorator):
"""Decorator that adds vanilla to coffee."""
def get_cost(self) -> float:
"""Get cost with vanilla added.
Returns:
Cost plus 0.7 for vanilla.
"""
return self._coffee.get_cost() + 0.7
def get_description(self) -> str:
"""Get description with vanilla.
Returns:
Description with vanilla added.
"""
return self._coffee.get_description() + ", Vanilla"
|
get_cost()
Get cost with vanilla added.
Returns:
| Type |
Description |
float
|
Cost plus 0.7 for vanilla.
|
Source code in src/design_patterns/structural/decorator.py
| def get_cost(self) -> float:
"""Get cost with vanilla added.
Returns:
Cost plus 0.7 for vanilla.
"""
return self._coffee.get_cost() + 0.7
|
get_description()
Get description with vanilla.
Returns:
| Type |
Description |
str
|
Description with vanilla added.
|
Source code in src/design_patterns/structural/decorator.py
| def get_description(self) -> str:
"""Get description with vanilla.
Returns:
Description with vanilla added.
"""
return self._coffee.get_description() + ", Vanilla"
|
WhippedCreamDecorator
Bases: CoffeeDecorator
Decorator that adds whipped cream to coffee.
Source code in src/design_patterns/structural/decorator.py
| class WhippedCreamDecorator(CoffeeDecorator):
"""Decorator that adds whipped cream to coffee."""
def get_cost(self) -> float:
"""Get cost with whipped cream added.
Returns:
Cost plus 0.8 for whipped cream.
"""
return self._coffee.get_cost() + 0.8
def get_description(self) -> str:
"""Get description with whipped cream.
Returns:
Description with whipped cream added.
"""
return self._coffee.get_description() + ", Whipped Cream"
|
get_cost()
Get cost with whipped cream added.
Returns:
| Type |
Description |
float
|
Cost plus 0.8 for whipped cream.
|
Source code in src/design_patterns/structural/decorator.py
| def get_cost(self) -> float:
"""Get cost with whipped cream added.
Returns:
Cost plus 0.8 for whipped cream.
"""
return self._coffee.get_cost() + 0.8
|
get_description()
Get description with whipped cream.
Returns:
| Type |
Description |
str
|
Description with whipped cream added.
|
Source code in src/design_patterns/structural/decorator.py
| def get_description(self) -> str:
"""Get description with whipped cream.
Returns:
Description with whipped cream added.
"""
return self._coffee.get_description() + ", Whipped Cream"
|