designpattern.site

Decorator Pattern

Attach additional responsibilities to an object dynamically by wrapping it in decorator objects that share the same interface.

Overview

Adding new behavior to a class sounds simple until you face the combinatorial problem. You have a TextMessage, and you need versions that are encrypted, compressed, logged, or any combination of those three. Subclassing every combination gives you eight classes before you have even shipped the first feature.

The Decorator pattern is a structural design pattern that solves this by wrapping an object inside another object that shares the same interface. The wrapper adds its behavior before or after delegating to the wrapped object. Because each decorator is interchangeable with what it wraps, you can stack decorators arbitrarily at runtime without altering a single class.

This is the same mechanic you already use every day in middleware pipelines, stream wrappers, and higher-order functions — the pattern just gives that mechanic a name and a formal structure.

Problem & Motivation

Imagine you are building a data export pipeline. Raw records need to travel through a series of processing steps before they reach the network: compress the payload to save bandwidth, then encrypt it for security, then log the byte count for monitoring. Not every export job needs all three steps — some are local and need no encryption, some are tiny and need no compression.

A naive subclassing approach produces classes like CompressedExporter, EncryptedExporter, LoggedExporter, CompressedEncryptedExporter, and so on. Each new processing step doubles the number of subclasses you have to maintain. The classes are also rigid: changing the compression algorithm means touching every subclass that includes compression.

The decorator approach composes behavior instead of inheriting it. You write CompressionDecorator, EncryptionDecorator, and LoggingDecorator once. At runtime, wrap them in any order around any exporter. No subclass explosion, and each concern lives in exactly one class.

Class Diagram

Loading diagram...

The key structural points are:

  • DataExporter is the component interface — both the concrete component and all decorators implement it, making them interchangeable.
  • BaseExporter is the concrete component — the real object being decorated.
  • ExporterDecorator is the abstract decorator — it holds a reference to a DataExporter and delegates calls to it. Subclasses extend this.
  • Each concrete decorator calls the wrapped object's export() and adds its own behavior before or after.

Implementation

All four implementations model the same scenario: a BaseExporter that returns data as-is, wrapped by CompressionDecorator, EncryptionDecorator, and LoggingDecorator. Any combination can be stacked at runtime.

// Component interface
interface DataExporter {
  export(data: string): string;
}

// Concrete component
class BaseExporter implements DataExporter {
  export(data: string): string {
    return data;
  }
}

// Abstract decorator — delegates to the wrapped component
abstract class ExporterDecorator implements DataExporter {
  constructor(protected wrapped: DataExporter) {}

  export(data: string): string {
    return this.wrapped.export(data);
  }
}

// Concrete decorators
class CompressionDecorator extends ExporterDecorator {
  export(data: string): string {
    const result = this.wrapped.export(data);
    return `[compressed(${result})]`;
  }
}

class EncryptionDecorator extends ExporterDecorator {
  export(data: string): string {
    const result = this.wrapped.export(data);
    return `[encrypted(${result})]`;
  }
}

class LoggingDecorator extends ExporterDecorator {
  export(data: string): string {
    const result = this.wrapped.export(data);
    console.log(`Exported ${result.length} bytes`);
    return result;
  }
}

// Usage — stack decorators at runtime
const exporter: DataExporter = new LoggingDecorator(
  new EncryptionDecorator(
    new CompressionDecorator(
      new BaseExporter()
    )
  )
);

console.log(exporter.export("hello world"));
// Logs: Exported 34 bytes
// Output: [encrypted([compressed(hello world)])]

Order Matters

Decorators execute from the outermost wrapper inward, and their results propagate back outward. Wrapping encryption outside compression means the compressed bytes are encrypted — the correct order for security. Reversing the order encrypts first, then compresses encrypted data, which is both less efficient and less secure. Always think about decorator order as a deliberate part of your design, not an implementation detail.

Real-World Examples

HTTP middleware in Express / Koa / Go's net/http — Every middleware function in Express is a decorator. app.use(cors()) wraps the request handler to add CORS headers. app.use(rateLimit()) adds throttling. Each middleware calls next() to delegate to the wrapped handler, matching the Decorator pattern's delegation model exactly. You stack them in any order without modifying route handlers.

Java I/O streams — The Java standard library is the canonical textbook example. new BufferedReader(new InputStreamReader(new FileInputStream("file.txt"))) stacks three decorators. FileInputStream is the concrete component; InputStreamReader converts bytes to characters; BufferedReader adds line-by-line reading. Each layer adds behavior without touching the layers below.

Logging with structured fields — Libraries like zap (Go) and structlog (Python) let you create a base logger and then attach context — request IDs, user IDs, trace IDs — by wrapping the logger with additional fields. Each "child" logger decorates its parent, prepending its fields before delegating to the parent's write method. The application code logs to whatever the current context provides, with no knowledge of what fields are attached.

Pros and Cons

Advantages

Avoids subclass explosion — adding N independent behaviors via subclassing requires 2^N subclasses for every combination. Decorators keep it linear: one class per behavior.

Open/Closed Principle — existing classes are never modified when you need new behavior. You write a new decorator and compose it in.

Single Responsibility Principle — each decorator handles exactly one concern. Compression logic lives only in CompressionDecorator.

Runtime flexibility — you decide which decorators to apply and in what order at runtime, not at compile time. Different users or configurations can receive different stacks.

Transparent to callers — because decorators and components share the same interface, callers do not know or care whether they are talking to a base object or a ten-layer stack.

Disadvantages

Deep stacks are hard to debug — a stack trace through seven decorators is intimidating. Identifying which decorator introduced a bug requires careful reading.

Object identity breaks — each decorator is a distinct object. Code that relies on instanceof checks or reference equality may behave unexpectedly when working with a decorated object.

Order-sensitive behavior is implicit — there is no built-in mechanism to enforce or document that encryption must come after compression. Incorrect ordering is a runtime problem, not a compile-time one.

Interface bloat — every method on the component interface must be passed through every decorator, even decorators that only care about one method. In languages without default interface implementations, this creates boilerplate.

Not suitable for adding state visible to callers — decorators are cleanest when they add transparent cross-cutting behavior. If the new behavior needs to be queried by callers (e.g., "what compression algorithm is active?"), the shared interface must grow, or callers must cast down.

When to Use / When to Avoid

Use Decorator when:

  • You need to add behavior to individual objects, not to all instances of a class.
  • The behaviors you want to add are independent and composable in arbitrary combinations.
  • Subclassing is impractical because the combination space is too large.
  • You want to respect the Open/Closed Principle — adding features without touching existing code.
  • The behaviors are cross-cutting concerns (logging, caching, auth, compression) that apply to many different core components.

Avoid Decorator when:

  • The order of decoration is fixed and never varies — a single subclass or a simple wrapper function is simpler.
  • The new behavior needs to be introspectable by callers — adding a method that is not on the interface requires casting, which defeats the pattern's transparency.
  • The component interface is very wide — passing through dozens of methods in every decorator is noisy boilerplate. Consider a Proxy or a Facade instead.
  • You need to add state that the component tracks, not just behavior that passes through — inheritance or composition without the shared interface may be clearer.

Proxy — structurally identical to Decorator (both wrap an object behind the same interface), but with a different intent. Proxy controls access to the object — adding lazy loading, access control, or remote dispatch. Decorator adds behavior. The distinction is purpose, not structure.

Composite — both Decorator and Composite rely on recursive object composition. Composite treats a group of objects as a single object; Decorator adds behavior to a single object. A tree of Composites can be wrapped by a Decorator, and the Decorator cannot tell the difference.

Strategy — Strategy changes the algorithm an object uses by swapping an internal dependency. Decorator changes behavior by wrapping the object from the outside. Use Strategy when the core object should be aware of and switch between algorithms; use Decorator when the behavior is a transparent layer the object knows nothing about.

Chain of Responsibility — also passes a request through a sequence of handlers, but handlers can break the chain and stop propagation. Decorator always delegates. Use Chain of Responsibility when any handler should be able to absorb the request; use Decorator when every layer must process and pass through.

Design Tip

When you find yourself reaching for Decorator, first check whether your language or framework already provides it. Express middleware, Python's @functools.wraps decorators, Java's InputStream hierarchy, and Go's http.Handler wrapping are all Decorator in disguise. Recognizing the pattern in the tools you already use helps you apply it correctly in your own code — and helps you avoid reinventing it when the platform already offers a clean composition point.

On this page