Skip to content

Decorator

Decorator attaches additional responsibilities to an object dynamically by wrapping it in another object that shares its interface, giving a flexible alternative to subclassing for extending behaviour.

You have a data source that can read and write bytes. Now you want optional features: compress the data, encrypt it, maybe both, in some chosen order. Subclassing every combination — CompressedSource, EncryptedSource, CompressedEncryptedSource, EncryptedCompressedSource — explodes quickly and bakes the choices in at compile time. You cannot turn a feature on for one object and off for another at runtime.

Decorator solves this by composition. Each feature is a wrapper that implements the same interface as the thing it wraps, holds a reference to an inner object, adds its own behaviour, and then forwards to the inner one. Because a decorator is the interface, you can stack them: encryption around compression around the raw source. Each layer does its bit and passes the rest along. You assemble the exact behaviour you want at runtime, per object, with no new subclasses.

Inheritance fixes the added behaviour at compile time and applies it to every instance of the subclass; you cannot mix and match per object or change the stack later. Decorator moves that choice to runtime: the same base object can be wrapped with different combinations of decorators, in different orders, and you can add a new decorator without touching the base class or any existing wrapper. The cost is more small objects and the indirection of forwarding calls through each layer.

classDiagram
  class DataSource {
    <<interface>>
    +write(data) string
    +read() string
  }
  class FileSource {
    +write(data) string
    +read() string
  }
  class SourceDecorator {
    <<abstract>>
    #wrappee: DataSource
    +write(data) string
    +read() string
  }
  class CompressionDecorator
  class EncryptionDecorator
  DataSource <|.. FileSource
  DataSource <|.. SourceDecorator
  SourceDecorator <|-- CompressionDecorator
  SourceDecorator <|-- EncryptionDecorator
  SourceDecorator o--> DataSource : wraps
Decorators share the component interface and wrap an inner component
  • Component (DataSource) — the interface shared by raw objects and decorators alike.
  • Concrete Component (FileSource) — the base object whose behaviour gets extended.
  • Decorator (SourceDecorator) — an abstract wrapper that holds a component and forwards to it by default.
  • Concrete Decorator (CompressionDecorator, EncryptionDecorator) — adds behaviour before or after delegating to the wrapped component.

A data source is wrapped with compression and then encryption. On write, each layer transforms the data on the way in; on read, the layers reverse it on the way out.

interface DataSource {
write(data: string): string;
read(stored: string): string;
}
class FileSource implements DataSource {
write = (data: string) => data;
read = (stored: string) => stored;
}
class CompressionDecorator implements DataSource {
constructor(private readonly inner: DataSource) {}
write = (data: string) => this.inner.write(`zip(${data})`);
read = (stored: string) => this.inner.read(stored).replace(/^zip\((.*)\)$/, '$1');
}
class EncryptionDecorator implements DataSource {
constructor(private readonly inner: DataSource) {}
write = (data: string) => this.inner.write(`enc(${data})`);
read = (stored: string) => this.inner.read(stored).replace(/^enc\((.*)\)$/, '$1');
}
const source = new EncryptionDecorator(new CompressionDecorator(new FileSource()));
const stored = source.write('hello');
console.log(stored); // zip(enc(hello))
console.log(source.read(stored)); // hello
  • Pro: add or remove responsibilities at runtime, per object, without touching the base class.
  • Pro: avoids the subclass explosion of every feature combination.
  • Pro: each decorator is a small, single-purpose, independently testable unit.
  • Con: a deep stack of tiny wrappers is hard to debug and step through.
  • Con: order matters — encryption-then-compression differs from compression-then-encryption — and the wiring is easy to get wrong.
  • Adapter wraps to change an interface; Decorator wraps while keeping the same interface and adding behaviour.
  • Composite shares the recursive-wrapping idea but aggregates many children rather than enhancing one.
  • Proxy also wraps an object with the same interface, but to control access rather than to add features.
How does a Decorator add behaviour to an object?
What advantage does Decorator have over inheritance here?
What does a decorator hold a reference to?
What distinguishes Decorator from Proxy despite the similar shape?