Decorator Pattern

Decorator Pattern

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

Intent

Decorator wraps a Component object (ConcreteComponent) with a Decorator that implements the same interface, delegating all calls to the wrapped object while adding behaviour before or after the delegation. Multiple decorators can be stacked; each layer adds one responsibility. This avoids the combinatorial subclass explosion that occurs when N features can be independently applied — Logger + Compressor + Encryptor would require 7 subclass combinations without Decorator, but only 3 decorator classes with it.

When NOT to Use

  • The component interface is large — each Decorator must implement all methods, even those it does not modify; the boilerplate burden becomes prohibitive
  • Only one or two responsibilities need to be added — subclassing or simple composition is clearer
  • Order of decoration matters and must be enforced — a Builder constructing the chain explicitly gives more control
  • The stack depth would exceed 4 layers — consider Pipes and Filters (see max-depth heuristic below)

When to Use

  • Add responsibilities to individual objects without affecting other objects of the same class
  • Responsibilities can be added or withdrawn at runtime
  • Subclassing would produce a combinatorial explosion of variants (Logger + Compressor + Encryptor = 7 subclasses without Decorator)

How It Works

Participants: Component (interface), ConcreteComponent (base object being wrapped), Decorator (abstract, holds a Component reference, delegates all calls to it), ConcreteDecorator (extends Decorator, adds behaviour before/after delegation).

The Decorator implements the same interface as what it wraps — this is what allows transparent stacking. A client holding a Component reference cannot tell whether it holds the ConcreteComponent directly or a chain of Decorators wrapping it. The innermost object in the chain is always the ConcreteComponent; each outer layer adds one concern.

Class Diagram

classDiagram
    class Component {
        <<interface>>
        +operation()*
    }
    class ConcreteComponent {
        +operation()
    }
    class Decorator {
        <<abstract>>
        #wrapped : Component
        +operation()
    }
    class ConcreteDecoratorA {
        +operation()
        +addedBehavior()
    }
    class ConcreteDecoratorB {
        +operation()
        +addedBehavior()
    }
    Component <|.. ConcreteComponent
    Component <|.. Decorator
    Decorator <|-- ConcreteDecoratorA
    Decorator <|-- ConcreteDecoratorB
    Decorator o-- Component : wraps

    note for Decorator "Each decorator wraps a Component\nand can chain: D_B( D_A( Concrete ) )"

TypeScript Example

// Decorator — TypeScript (manual wrapper — the GoF structural pattern)
// Source: GoF Design Patterns, p.175
 
interface Logger {
  log(message: string): void;
}
 
class ConsoleLogger implements Logger {
  log(message: string) { console.log(message); }
}
 
class TimestampDecorator implements Logger {
  constructor(private wrapped: Logger) {}
  log(message: string) {
    this.wrapped.log(`[${new Date().toISOString()}] ${message}`);
  }
}
 
// Stack decorators: at most 3-4 deep (see max-depth heuristic)
const logger = new TimestampDecorator(new ConsoleLogger());
logger.log("hello");

Java Example

// Decorator — Java (manual wrapper)
// Source: GoF Design Patterns, p.175
interface Logger { void log(String message); }
 
class ConsoleLogger implements Logger {
    public void log(String msg) { System.out.println(msg); }
}
 
class TimestampDecorator implements Logger {
    private final Logger wrapped;
    TimestampDecorator(Logger l) { this.wrapped = l; }
    public void log(String msg) {
        wrapped.log("[" + Instant.now() + "] " + msg);
    }
}

Max-Depth Heuristic

Max-depth heuristic: More than 3-4 layers of Decorator wrapping is a warning sign. Deep chains make debugging hard (stack traces traverse every wrapper) and reading order unclear (innermost wrapper applies first). At 4+ layers, consider a Pipeline or Pipes-and-Filters approach (see EIP-Pipes-and-Filters-Pattern, created Phase 11) or consolidate related decorators into a single wrapper.

TypeScript Decorator Syntax Note

TypeScript Decorator Syntax Note: TypeScript's @decorator syntax — legacy experimentalDecorators: true (TS 4.x and earlier) and the TC39 Stage 3 proposal (TS 5.0+, different semantics) — is a language feature for class and member metadata. It shares the name "decorator" with this GoF structural pattern but differs in mechanism. The GoF Decorator is a structural composition pattern using manual wrapping; TypeScript's @decorator syntax is a compile-time transformation. Prefer the manual-wrapping approach shown above when the GoF pattern is the intent. Use @decorator syntax for cross-cutting concerns (logging, validation, serialisation) when the framework (NestJS, Angular) supports it.

Lineage Forward

Decorator → EIP-Pipes-and-Filters-Pattern (Phase 11) → Sidecar-Pattern (Phase 14). Each layer extends the same interface; Pipes and Filters generalises this to a processing pipeline.

PatternRelationship
Proxy-PatternBoth wrap an object with the same interface; Decorator adds behaviour, Proxy controls access. See Decorator-vs-Proxy-vs-Adapter
Adapter-PatternAdapter changes the interface; Decorator preserves it. See Decorator-vs-Proxy-vs-Adapter
Composite-PatternBoth use a component reference; Composite manages a tree, Decorator wraps a single object
EIP-Pipes-and-Filters-PatternGeneralisation of Decorator stacking to a processing pipeline (created Phase 11)
  • gRPC-Service-Design — gRPC interceptors implement cross-cutting concerns by wrapping the call with the same interface — the Decorator structural pattern applied at the RPC layer
  • Component-Composition-Patterns — Higher-Order Components (HOC) in React are the direct application of the Decorator pattern to UI components: a HOC wraps a component with the same interface and adds cross-cutting behaviour (auth, analytics, error boundaries). Angular @Directive applies the same structural pattern at the DOM element level.

Sources

  • Gamma, Helm, Johnson, Vlissides — Design Patterns, Addison-Wesley, 1994, p.175

Notes that link here: GoF-Patterns-MOC, Decorator-vs-Proxy-vs-Adapter