Decorator Pattern
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Problem
You want to add new functionality to objects without modifying their classes. Inheritance isn't flexible enough because you'd need many subclasses for each combination.
Solution
The Decorator pattern uses composition to wrap objects with decorators that add new functionality.
Implementation
from abc import ABC, abstractmethod
from typing import Optional
# ============ COFFEE DECORATOR EXAMPLE ============
class Beverage(ABC):
"""Component - base interface"""
@abstractmethod
def get_description(self) -> str:
pass
@abstractmethod
def get_cost(self) -> float:
pass
class SimpleCoffee(Beverage):
"""Concrete Component - simple beverage"""
def get_description(self) -> str:
return "Simple Coffee"
def get_cost(self) -> float:
return 2.0
class BeverageDecorator(Beverage):
"""Abstract Decorator - wraps a beverage"""
def __init__(self, beverage: Beverage):
self.beverage = beverage
class MilkDecorator(BeverageDecorator):
"""Concrete Decorator - adds milk"""
def get_description(self) -> str:
return self.beverage.get_description() + ", Milk"
def get_cost(self) -> float:
return self.beverage.get_cost() + 0.5
class SugarDecorator(BeverageDecorator):
"""Concrete Decorator - adds sugar"""
def get_description(self) -> str:
return self.beverage.get_description() + ", Sugar"
def get_cost(self) -> float:
return self.beverage.get_cost() + 0.25
class ChocolateDecorator(BeverageDecorator):
"""Concrete Decorator - adds chocolate"""
def get_description(self) -> str:
return self.beverage.get_description() + ", Chocolate"
def get_cost(self) -> float:
return self.beverage.get_cost() + 1.0
# ============ I/O STREAMS DECORATOR EXAMPLE ============
class DataStream(ABC):
@abstractmethod
def read(self) -> str:
pass
@abstractmethod
def write(self, data: str):
pass
class FileStream(DataStream):
"""Concrete Component - raw file stream"""
def __init__(self, filename: str):
self.filename = filename
def read(self) -> str:
return f"Reading from {self.filename}"
def write(self, data: str):
print(f"Writing to {self.filename}: {data}")
class StreamDecorator(DataStream):
"""Abstract Decorator"""
def __init__(self, stream: DataStream):
self.stream = stream
class BufferedStream(StreamDecorator):
"""Decorator - adds buffering"""
def __init__(self, stream: DataStream):
super().__init__(stream)
self.buffer = []
def read(self) -> str:
return f"[BUFFERED] {self.stream.read()}"
def write(self, data: str):
self.buffer.append(data)
if len(self.buffer) >= 3:
print(f"Flushing buffer: {self.buffer}")
self.buffer = []
class EncryptedStream(StreamDecorator):
"""Decorator - adds encryption"""
def read(self) -> str:
return f"[DECRYPTED] {self.stream.read()}"
def write(self, data: str):
encrypted = data.encode("rot13") # Simple encryption
self.stream.write(encrypted)
class CompressedStream(StreamDecorator):
"""Decorator - adds compression"""
def read(self) -> str:
return f"[DECOMPRESSED] {self.stream.read()}"
def write(self, data: str):
compressed = f"[COMPRESSED: {len(data)} -> {len(data)//2} bytes]"
self.stream.write(compressed)
# ============ PIZZA DECORATOR EXAMPLE ============
class Pizza(ABC):
@abstractmethod
def get_description(self) -> str:
pass
@abstractmethod
def get_price(self) -> float:
pass
class BasicPizza(Pizza):
def get_description(self) -> str:
return "Basic Pizza"
def get_price(self) -> float:
return 5.0
class PizzaDecorator(Pizza):
def __init__(self, pizza: Pizza):
self.pizza = pizza
class CheeseTopping(PizzaDecorator):
def get_description(self) -> str:
return self.pizza.get_description() + " + Cheese"
def get_price(self) -> float:
return self.pizza.get_price() + 1.0
class PepperoniTopping(PizzaDecorator):
def get_description(self) -> str:
return self.pizza.get_description() + " + Pepperoni"
def get_price(self) -> float:
return self.pizza.get_price() + 1.5
class MushroomTopping(PizzaDecorator):
def get_description(self) -> str:
return self.pizza.get_description() + " + Mushroom"
def get_price(self) -> float:
return self.pizza.get_price() + 0.75
# ============ WINDOW DECORATOR EXAMPLE ============
class Window(ABC):
@abstractmethod
def render(self):
pass
class SimpleWindow(Window):
def render(self):
print("Rendering Simple Window")
class WindowDecorator(Window):
def __init__(self, window: Window):
self.window = window
class VerticalScrollBar(WindowDecorator):
def render(self):
self.window.render()
print(" + Adding Vertical Scrollbar")
class HorizontalScrollBar(WindowDecorator):
def render(self):
self.window.render()
print(" + Adding Horizontal Scrollbar")
class BorderDecorator(WindowDecorator):
def render(self):
self.window.render()
print(" + Adding Border")
# Demo
if __name__ == "__main__":
print("=== Coffee Decorator ===")
# Plain coffee
coffee = SimpleCoffee()
print(f"{coffee.get_description()}: ${coffee.get_cost()}")
# Coffee with milk
coffee = MilkDecorator(SimpleCoffee())
print(f"{coffee.get_description()}: ${coffee.get_cost()}")
# Coffee with milk and sugar
coffee = SugarDecorator(MilkDecorator(SimpleCoffee()))
print(f"{coffee.get_description()}: ${coffee.get_cost()}")
# Coffee with milk, sugar, and chocolate
coffee = ChocolateDecorator(
SugarDecorator(MilkDecorator(SimpleCoffee()))
)
print(f"{coffee.get_description()}: ${coffee.get_cost()}\n")
print("=== Pizza Toppings ===")
pizza = BasicPizza()
print(f"{pizza.get_description()}: ${pizza.get_price()}")
pizza = CheeseTopping(BasicPizza())
print(f"{pizza.get_description()}: ${pizza.get_price()}")
pizza = PepperoniTopping(CheeseTopping(BasicPizza()))
print(f"{pizza.get_description()}: ${pizza.get_price()}")
pizza = MushroomTopping(PepperoniTopping(CheeseTopping(BasicPizza())))
print(f"{pizza.get_description()}: ${pizza.get_price()}\n")
print("=== Stream Decorators ===")
file = FileStream("data.txt")
print(file.read())
buffered = BufferedStream(file)
print(buffered.read())
encrypted = EncryptedStream(file)
encrypted.write("Secret message")
compressed = CompressedStream(file)
compressed.write("Large data here")
print()
print("=== Window Decorators ===")
window = SimpleWindow()
window.render()
print()
window = VerticalScrollBar(SimpleWindow())
window.render()
print()
window = BorderDecorator(
HorizontalScrollBar(
VerticalScrollBar(SimpleWindow())
)
)
window.render()
Key Concepts
- Component: Common interface for original and decorated objects
- Concrete Component: Base object that can be decorated
- Decorator: Abstract class wrapping component
- Concrete Decorators: Add specific functionality
- Composition over Inheritance: Flexible functionality addition
- Runtime stacking: Decorators can be stacked in any order
When to Use
✅ Add responsibilities to objects dynamically
✅ Avoid class explosion from inheritance
✅ Add/remove features at runtime
✅ Need flexible combinations of features
Interview Q&A
Q1: What's the difference between Decorator and Proxy?
A: - Decorator: Adds NEW functionality. Enhances behavior. - Proxy: Controls access. Manages behavior.
# Decorator: Adds functionality
class LoggingDecorator:
def operation(self):
print("Logging...")
self.obj.operation() # Then do original
# Proxy: Controls access
class ProxyUser:
def operation(self):
if self.has_permission(): # Check first
self.obj.operation()
Q2: What's the difference between Decorator and Composite?
A: - Composite: Tree structure, represents hierarchies - Decorator: Linear wrapping, adds functionality
# Composite: Tree
folder.add(subfolder)
folder.add(file)
# Decorator: Linear chain
decorated = Decorator1(Decorator2(Decorator3(object)))
Q3: How would you handle decorator removal?
A:
class RemovableDecorator(Beverage):
def __init__(self, beverage: Beverage):
self.beverage = beverage
def remove_decorator(self) -> Beverage:
return self.beverage
# Usage
coffee = MilkDecorator(SimpleCoffee())
plain_coffee = coffee.remove_decorator()
Q4: How would you prevent infinite decorator chains?
A:
class RestrictedDecorator(Beverage):
def __init__(self, beverage: Beverage):
if isinstance(beverage, RestrictedDecorator):
raise TypeError("Cannot nest RestrictedDecorator")
self.beverage = beverage
Q5: How would you test decorated objects?
A:
from unittest.mock import Mock
# Create mock component
mock_beverage = Mock(spec=Beverage)
mock_beverage.get_description.return_value = "Mock"
mock_beverage.get_cost.return_value = 1.0
# Test decorator with mock
decorated = MilkDecorator(mock_beverage)
assert "Milk" in decorated.get_description()
assert decorated.get_cost() == 1.5
Trade-offs
✅ Pros: Flexible, avoids inheritance explosion, add/remove at runtime
❌ Cons: Complex object creation, many small classes, hard to debug
Real-World Examples
- Java I/O Streams (BufferedInputStream, DataInputStream, etc.)
- Text editors (spell check, grammar check, formatting decorators)
- UI frameworks (scrollbars, borders, shadows on windows)
- HTTP clients (logging, compression, encryption decorators)