Decorator Pattern
Intent:
Adds behavior or responsibilities to individual objects dynamically without affecting other objects. Allows you to attach new behaviors to objects by placing them inside wrapper objects.
When to Use:
When you need to add functionality to objects at runtime, and you want to avoid creating a large number of subclasses for every possible combination of features.
Key Characteristics:
- Adds behavior dynamically to objects
- Wraps objects to add new functionality
- Allows multiple decorators to be stacked
- More flexible than inheritance
Think of it like adding toppings to a pizza - you start with a base pizza, then add cheese, then pepperoni, then mushrooms. Each topping decorates the pizza without changing the base.
🍕 Example: Pizza with Toppings
Let's say we're building a pizza ordering system. We have a base pizza, and customers can add multiple toppings (cheese, pepperoni, mushrooms, etc.). Each topping adds to the cost and description.
Step 1: Define Base Component Interface
# pizza_interface.py from abc import ABC, abstractmethod class Pizza(ABC): @abstractmethod def get_description(self): pass # Returns pizza description @abstractmethod def get_cost(self): pass # Returns pizza cost# pizza_interface.py from abc import ABC, abstractmethod class Pizza(ABC): @abstractmethod def get_description(self): pass # Returns pizza description @abstractmethod def get_cost(self): pass # Returns pizza cost
Step 2: Create Base Pizza
# base_pizza.py from pizza_interface import Pizza class BasePizza(Pizza): def get_description(self): return "Plain Pizza" def get_cost(self): return 100 # Base price# base_pizza.py from pizza_interface import Pizza class BasePizza(Pizza): def get_description(self): return "Plain Pizza" def get_cost(self): return 100 # Base price
Step 3: Create Base Decorator
# pizza_decorator.py from pizza_interface import Pizza class PizzaDecorator(Pizza): def __init__(self, pizza): self.pizza = pizza # Wraps the pizza def get_description(self): return self.pizza.get_description() def get_cost(self): return self.pizza.get_cost()# pizza_decorator.py from pizza_interface import Pizza class PizzaDecorator(Pizza): def __init__(self, pizza): self.pizza = pizza # Wraps the pizza def get_description(self): return self.pizza.get_description() def get_cost(self): return self.pizza.get_cost()
Step 4: Create Cheese Decorator
# cheese_decorator.py from pizza_decorator import PizzaDecorator class CheeseDecorator(PizzaDecorator): def get_description(self): return self.pizza.get_description() + ", Extra Cheese" def get_cost(self): return self.pizza.get_cost() + 30 # Cheese costs ₹30# cheese_decorator.py from pizza_decorator import PizzaDecorator class CheeseDecorator(PizzaDecorator): def get_description(self): return self.pizza.get_description() + ", Extra Cheese" def get_cost(self): return self.pizza.get_cost() + 30 # Cheese costs ₹30
Step 5: Create Pepperoni Decorator
# pepperoni_decorator.py from pizza_decorator import PizzaDecorator class PepperoniDecorator(PizzaDecorator): def get_description(self): return self.pizza.get_description() + ", Pepperoni" def get_cost(self): return self.pizza.get_cost() + 50 # Pepperoni costs ₹50# pepperoni_decorator.py from pizza_decorator import PizzaDecorator class PepperoniDecorator(PizzaDecorator): def get_description(self): return self.pizza.get_description() + ", Pepperoni" def get_cost(self): return self.pizza.get_cost() + 50 # Pepperoni costs ₹50
Step 6: Create Mushroom Decorator
# mushroom_decorator.py from pizza_decorator import PizzaDecorator class MushroomDecorator(PizzaDecorator): def get_description(self): return self.pizza.get_description() + ", Mushrooms" def get_cost(self): return self.pizza.get_cost() + 40 # Mushrooms cost ₹40# mushroom_decorator.py from pizza_decorator import PizzaDecorator class MushroomDecorator(PizzaDecorator): def get_description(self): return self.pizza.get_description() + ", Mushrooms" def get_cost(self): return self.pizza.get_cost() + 40 # Mushrooms cost ₹40
Step 7: Use the Decorators (Client Code)
# main.py from base_pizza import BasePizza from cheese_decorator import CheeseDecorator from pepperoni_decorator import PepperoniDecorator from mushroom_decorator import MushroomDecorator # Order 1: Plain pizza print("Order 1:") pizza1 = BasePizza() print(f"Description: {pizza1.get_description()}") print(f"Cost: ₹{pizza1.get_cost()}") # Order 2: Pizza with cheese print("\nOrder 2:") pizza2 = CheeseDecorator(BasePizza()) print(f"Description: {pizza2.get_description()}") print(f"Cost: ₹{pizza2.get_cost()}") # Order 3: Pizza with cheese and pepperoni print("\nOrder 3:") pizza3 = PepperoniDecorator(CheeseDecorator(BasePizza())) print(f"Description: {pizza3.get_description()}") print(f"Cost: ₹{pizza3.get_cost()}") # Order 4: Pizza with all toppings print("\nOrder 4:") pizza4 = MushroomDecorator(PepperoniDecorator(CheeseDecorator(BasePizza()))) print(f"Description: {pizza4.get_description()}") print(f"Cost: ₹{pizza4.get_cost()}")# main.py from base_pizza import BasePizza from cheese_decorator import CheeseDecorator from pepperoni_decorator import PepperoniDecorator from mushroom_decorator import MushroomDecorator # Order 1: Plain pizza print("Order 1:") pizza1 = BasePizza() print(f"Description: {pizza1.get_description()}") print(f"Cost: ₹{pizza1.get_cost()}") # Order 2: Pizza with cheese print("\nOrder 2:") pizza2 = CheeseDecorator(BasePizza()) print(f"Description: {pizza2.get_description()}") print(f"Cost: ₹{pizza2.get_cost()}") # Order 3: Pizza with cheese and pepperoni print("\nOrder 3:") pizza3 = PepperoniDecorator(CheeseDecorator(BasePizza())) print(f"Description: {pizza3.get_description()}") print(f"Cost: ₹{pizza3.get_cost()}") # Order 4: Pizza with all toppings print("\nOrder 4:") pizza4 = MushroomDecorator(PepperoniDecorator(CheeseDecorator(BasePizza()))) print(f"Description: {pizza4.get_description()}") print(f"Cost: ₹{pizza4.get_cost()}")
Output:
Order 1: Description: Plain Pizza Cost: ₹100 Order 2: Description: Plain Pizza, Extra Cheese Cost: ₹130 Order 3: Description: Plain Pizza, Extra Cheese, Pepperoni Cost: ₹180 Order 4: Description: Plain Pizza, Extra Cheese, Pepperoni, Mushrooms Cost: ₹220
✅ Benefits
1. Flexibility:
- Add features dynamically at runtime
- Combine features in any order
2. Avoids Class Explosion:
- Don't need a class for every combination (PlainPizza, CheesePizza, PepperoniPizza, CheesePepperoniPizza, etc.)
- Just combine decorators as needed
3. Single Responsibility:
- Each decorator adds one specific feature
- Easy to understand and maintain
4. Open for Extension:
- Easy to add new toppings (decorators)
- No need to modify existing code
🎯 Key Points
- Decorator wraps objects to add behavior dynamically
- Can stack multiple decorators
- Each decorator adds one feature
- More flexible than creating many subclasses
- Original object remains unchanged
🌟 Quick Summary
- Problem: Need to add features to objects, but creating subclasses for every combination is impractical
- Solution: Use decorators to wrap objects and add features dynamically
- Benefit: Flexible feature combination without class explosion
- Example: Pizza with multiple toppings - each topping is a decorator
🎉 End
The Decorator Pattern helps you add features to objects flexibly. It's perfect when you need to combine features dynamically without creating too many classes!