100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Software Design

Decorator Pattern

The Decorator pattern attaches additional responsibilities to an object dynamically by wrapping it in one or more decorator objects that share its interface.

Structural PatternsIntermediate10 min readJul 10, 2026
Analogies

Adding Behavior Without Subclass Explosion

The Decorator pattern lets you attach new responsibilities to an individual object at runtime without altering the class of that object or affecting other instances of the same class. It works by defining a Component interface implemented both by the original (Concrete Component) and by a family of Decorator classes that also implement the same interface while holding a reference to a wrapped Component. Each decorator forwards calls to the wrapped object, then adds its own behavior before or after the delegated call. This avoids the combinatorial subclass explosion you'd get if you tried to create a distinct subclass for every combination of optional features.

🏏

Cricket analogy: Virat Kohli's core batting technique doesn't change, but a coach layers on situational adjustments (a slog-sweep for spin, an anchor role in a run chase) without creating an entirely new 'Kohli subclass' for each match situation, exactly like decorators layering behavior onto a stable core object.

Structural Mechanics: The Same Interface, All the Way Down

The critical structural detail is that every decorator implements the exact same interface as the component it wraps, so a decorated object is indistinguishable from an undecorated one from the client's point of view, and decorators can be nested arbitrarily deep. Java's I/O library is a canonical real-world example: a FileReader can be wrapped in a BufferedReader for buffering, which can itself be wrapped in a LineNumberReader for line tracking, each layer adding behavior while still exposing the same Reader-compatible surface. Because order determines which behavior executes first, wrapping a stream with compression before encryption produces a different result than encryption before compression.

🏏

Cricket analogy: Whether a bowler wraps 'change of pace' around 'seam movement' or the reverse produces a different delivery outcome for the batter, just as wrapping an encryption decorator around compression versus compression around encryption changes the final output.

A Worked Example

typescript
interface Notifier {
  send(message: string): void;
}

class BaseNotifier implements Notifier {
  send(message: string) {
    console.log(`Email: ${message}`);
  }
}

abstract class NotifierDecorator implements Notifier {
  constructor(protected wrapped: Notifier) {}
  send(message: string) {
    this.wrapped.send(message);
  }
}

class SMSDecorator extends NotifierDecorator {
  send(message: string) {
    super.send(message);
    console.log(`SMS: ${message}`);
  }
}

class SlackDecorator extends NotifierDecorator {
  send(message: string) {
    super.send(message);
    console.log(`Slack: ${message}`);
  }
}

// Compose at runtime: Email -> SMS -> Slack, all behind the same Notifier interface
const notifier: Notifier = new SlackDecorator(new SMSDecorator(new BaseNotifier()));
notifier.send('Deployment finished');
// Output: Email: ...  SMS: ...  Slack: ...

Don't confuse this GoF structural pattern with Python's @decorator syntax. Python decorators wrap functions at definition time using syntactic sugar and often replace the function entirely; the GoF Decorator pattern wraps object instances at runtime and preserves a shared interface so wrapped and unwrapped objects remain interchangeable. They share a name and a wrapping idea, but the mechanics and typical use cases differ.

Deep decorator chains can make debugging painful: a stack trace through five nested decorators obscures which layer actually produced a given side effect or exception. Keep each decorator focused on one responsibility, and consider logging or naming decorators clearly so the chain is inspectable at runtime.

When Decorator Is the Right Tool

Reach for Decorator when the set of optional behaviors is open-ended, when combinations need to be assembled at runtime rather than fixed at compile time, and when you want clients to remain unaware of how many layers are wrapping the core object. Avoid it when there are only one or two fixed variations, since a simple conditional or a couple of subclasses is clearer than the ceremony of defining a decorator hierarchy. Decorator also composes well with Composite: a UI toolkit might use Composite to build a tree of widgets and Decorator to add a scrollbar or border to any widget in that tree without the widget classes needing to know about scrolling or borders themselves.

🏏

Cricket analogy: A franchise adds situational specialists (a death-overs bowling coach, a spin-fielding coach) as needed for a given tournament, but wouldn't build a decorator layer for something with only two fixed states like 'home game vs away game', where a simple flag suffices.

  • Decorator attaches new responsibilities to an object at runtime by wrapping it in objects that share its interface.
  • It avoids the subclass explosion that would result from creating a class for every combination of optional features.
  • Every decorator implements the same interface as the wrapped component, so decorated and undecorated objects are interchangeable to the client.
  • Decorators can be nested arbitrarily, and the order of wrapping changes the resulting behavior.
  • Java's I/O stream classes (BufferedReader wrapping FileReader) are a classic real-world example.
  • The GoF Decorator pattern is structurally distinct from Python's @decorator syntax despite the shared name.
  • Deep decorator chains can hurt debuggability; keep each decorator single-purpose.

Practice what you learned

Was this page helpful?

Topics covered

#SoftwareDesign#DesignPatternsStudyNotes#SoftwareEngineering#DecoratorPattern#Decorator#Pattern#Adding#Behavior#StudyNotes#SkillVeris