Structural Pattern

Decorator

Attach additional responsibilities to an object dynamically, providing a flexible alternative to subclassing for extending functionality.

Wrap to add features.

The Decorator pattern lets you add new behavior to an object by wrapping it in another object that shares the same interface. Think of a coffee: start with plain coffee, wrap it with "milk," then wrap that with "sugar." Each layer adds to the price and description while still being a "beverage."

bad.py
# Problem: subclass explosion for every feature combo.
class Coffee: ...
class MilkCoffee(Coffee): ...
class SugarCoffee(Coffee): ...
class MilkSugarCoffee(Coffee): ...
# Add Whip? Double the subclasses again.
good.py
# Fix: Decorator — wrap a Coffee to add behavior without new subclasses.
class Coffee:
    def cost(self): return 2
    def describe(self): return "espresso"

class Milk:
    def __init__(self, inner): self.inner = inner
    def cost(self): return self.inner.cost() + 0.5
    def describe(self): return self.inner.describe() + ", milk"

class Sugar:
    def __init__(self, inner): self.inner = inner
    def cost(self): return self.inner.cost() + 0.2
    def describe(self): return self.inner.describe() + ", sugar"

# Stack freely: Sugar(Milk(Coffee()))
c = Sugar(Milk(Coffee()))
print(c.describe(), c.cost())     # espresso, milk, sugar  2.7

Composition over subclassing.

Participants: a Component interface, a ConcreteComponent (the base object), a Decorator (implements Component and holds a Component), and ConcreteDecorators that add behavior before/after delegating to the wrapped object.

  • Avoids class explosion — combining N features by subclassing needs 2ᴺ classes; decorators compose at runtime.
  • Transparent — since decorators share the interface, clients don't know they're talking to a decorated object.
  • Order matters — the wrapping order can change the result.

Trade-offs and relations.

  • Java I/O is the canonical example: BufferedInputStream, DataInputStream, etc. wrap streams to add buffering, typed reads, and more.
  • vs Inheritance — decorators add behavior at runtime and can be combined in any order; subclassing is static and fixed at compile time.
  • vs Proxy — same interface, but Proxy controls access rather than enriching behavior; vs Adapter — Adapter changes the interface.
  • Downsides — many small wrapper objects, harder debugging (deep wrap chains), and identity issues (a decorated object is not == the original).