Decorator Pattern

Updated Nov 23, 2025

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

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

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()

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

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

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

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()}")

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!