🧱 Structural

Decorator Pattern

Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. They wrap the original object and add new behavior before or after delegating to the wrapped object's methods.

Problem

You need to add behavior to individual objects, not to an entire class. Inheritance is static—you can't alter the behavior of an existing object at runtime. Using inheritance to combine every possible behavior variation leads to a class explosion. If you need to combine compression, encryption, and buffering for a data source, you'd need subclasses for every permutation.

Solution

Create a wrapper class (decorator) that has the same interface as the target object. The decorator contains a reference to the wrapped object and forwards all requests to it, but adds extra behavior before or after. Since decorators implement the same interface, the client doesn't know whether it works with the original or wrapped object. Multiple decorators can be stacked.

Participants

RoleResponsibility
ComponentDeclares the common interface for wrappers and wrapped objects
ConcreteComponentA class of objects being wrapped; defines the basic behavior
DecoratorHas a reference to a Component and defines an interface matching Component's
ConcreteDecoratorDefines extra behaviors that can be added to components dynamically

TypeScript Example

TypeScript
interface HttpHandler {
  handle(request: string): string;
}

class BaseHandler implements HttpHandler {
  handle(request: string) {
    return `Response for ${request}`;
  }
}

class LoggingDecorator implements HttpHandler {
  constructor(private inner: HttpHandler) {}

  handle(request: string) {
    console.log(`[LOG] Incoming: ${request}`);
    const response = this.inner.handle(request);
    console.log(`[LOG] Outgoing: ${response}`);
    return response;
  }
}

class AuthDecorator implements HttpHandler {
  constructor(private inner: HttpHandler, private token: string) {}

  handle(request: string) {
    if (!this.token) throw new Error("Unauthorized");
    return this.inner.handle(`[auth:${this.token}] ${request}`);
  }
}

class CacheDecorator implements HttpHandler {
  private cache = new Map<string, string>();
  constructor(private inner: HttpHandler) {}

  handle(request: string) {
    if (this.cache.has(request)) return this.cache.get(request)!;
    const response = this.inner.handle(request);
    this.cache.set(request, response);
    return response;
  }
}

const handler = new CacheDecorator(
  new LoggingDecorator(
    new AuthDecorator(new BaseHandler(), "secret")
  )
);
console.log(handler.handle("GET /api/users"));

Real-World Example

Middleware stacks in Express.js, Koa, and similar HTTP frameworks are decorators. Each middleware wraps the next handler, adding logging, authentication, CORS, compression, or error handling. Java I/O streams also use decorators extensively—you wrap a FileInputStream with BufferedInputStream, then GZIPInputStream. Each layer adds functionality without modifying the others.

When to Use

  • You need to add responsibilities to objects dynamically and transparently, without affecting other objects
  • You want to combine multiple behaviors by wrapping an object in multiple decorators
  • Extension by subclassing is impractical due to a large number of possible combinations
  • You want to be able to remove responsibilities from an object at runtime

When Not to Use

  • The order of decorators matters and is hard for clients to manage correctly
  • You need to remove a specific decorator from the middle of a stack
  • The component interface is large, making wrapper implementation cumbersome

Understanding the Decorator Pattern

The Decorator pattern is one of the most important structural design patterns in object-oriented software design. Originally cataloged by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," this pattern continues to be a cornerstone of modern software architecture.

Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. They wrap the original object and add new behavior before or after delegating to the wrapped object's methods. The pattern involves the following key participants: Component, ConcreteComponent, Decorator, ConcreteDecorator. Each participant has a well-defined role, and the interactions between them create a flexible, maintainable structure that can evolve with changing requirements.

In TypeScript and modern JavaScript development, the Decorator pattern is particularly valuable because the language's type system and interface support allow you to express the pattern with compile-time safety. TypeScript generics can make Decorator implementations even more flexible and reusable while maintaining strong type checking throughout the pattern's structure.

Knowing when to apply the Decorator pattern is as important as knowing how. Common use cases include: You need to add responsibilities to objects dynamically and transparently, without affecting other objects; You want to combine multiple behaviors by wrapping an object in multiple decorators; Extension by subclassing is impractical due to a large number of possible combinations. However, overusing design patterns can lead to unnecessarily complex code. Always evaluate whether the pattern genuinely solves your problem or if a simpler approach would suffice.

The Decorator pattern is related to several other design patterns: Adapter, Composite, Proxy, Strategy, Chain of Responsibility. Understanding these relationships helps you choose the right pattern for your specific situation and combine patterns effectively when building complex systems.

Related Patterns

More Structural Patterns

Explore All Design Patterns

Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.