Decorator Pattern

The Decorator is a structural design pattern that allows behavior to be added to individual objects dynamically, without affecting other objects from the same class. It uses composition to wrap objects, extending functionality at runtime (e.g., adding logging or caching to methods).

Key Principles

Structure

Python Example: Coffee Shop (Beverage with Add-ons)

from abc import ABC, abstractmethod

# Component Interface
class Beverage(ABC):
    @abstractmethod
    def cost(self) -> float:
        pass

    @abstractmethod
    def description(self) -> str:
        pass

# Concrete Component
class Espresso(Beverage):
    def cost(self) -> float:
        return 1.99

    def description(self) -> str:
        return "Espresso"

class HouseBlend(Beverage):
    def cost(self) -> float:
        return 0.99

    def description(self) -> str:
        return "House Blend Coffee"

# Decorator (Abstract Wrapper)
class BeverageDecorator(Beverage):
    def __init__(self, beverage: Beverage):
        self.beverage = beverage

    def cost(self) -> float:
        return self.beverage.cost()

    def description(self) -> str:
        return self.beverage.description()

# Concrete Decorators (Add-ons)
class Milk(BeverageDecorator):
    def cost(self) -> float:
        return super().cost() + 0.10

    def description(self) -> str:
        return f"{super().description()}, Milk"

class Sugar(BeverageDecorator):
    def cost(self) -> float:
        return super().cost() + 0.05

    def description(self) -> str:
        return f"{super().description()}, Sugar"

class Whip(BeverageDecorator):
    def cost(self) -> float:
        return super().cost() + 0.15

    def description(self) -> str:
        return f"{super().description()}, Whip"

# Usage (Client Code)
if __name__ == "__main__":
    # Basic beverage
    espresso = Espresso()
    print(f"{espresso.description()}: ${espresso.cost()}")

    # Decorated (dynamic add-ons)
    decorated = Whip(Sugar(Milk(espresso)))
    print(f"{decorated.description()}: ${decorated.cost()}")

    # Another variant
    house_blend = HouseBlend()
    with_milk = Milk(house_blend)
    print(f"{with_milk.description()}: ${with_milk.cost()}")

Output:

Espresso: $1.99
Espresso, Milk, Sugar, Whip: $2.29
House Blend Coffee, Milk: $1.09

When to Use Decorator

Summary: Decorator wraps objects to add behavior incrementally via composition, enabling flexible extensions without altering the original class.