What It Is
The Decorator pattern adds behavior to an object by wrapping it in another object with the same interface. Each wrapper does its extra bit and passes the call on to what it wraps.
Because wrapper and wrapped share the interface, wrappers stack in any combination — like layers of clothing.
Note: this page wraps with classes, because the layers hold state (the object they wrap) and the nesting is easy to see. A wrapper can just as well be a function that takes a function and returns a new one — that is the same pattern in a smaller shape.
Example: Wrapping a Notifier
The plain notifier sends an email. One wrapper adds logging before the send; another adds an SMS after it. The caller still sees a plain Notifier.
Layers around a core
new WithLog(new WithSMS(new EmailNotifier())) reads inside-out: email at the core, SMS around it, logging outermost. The output shows that order — log line, email, then SMS. Any layer can be added, removed, or reordered without touching the others.
Output: TypeScript file and captured output
interface Notifier {
send(msg: string): void
}
class EmailNotifier implements Notifier {
send(msg: string): void {
console.log("email:", msg)
}
}
// Each decorator wraps a Notifier and adds one layer.
class WithLog implements Notifier {
private wrapped: Notifier
constructor(wrapped: Notifier) {
this.wrapped = wrapped
}
send(msg: string): void {
console.log("log: sending a notification")
this.wrapped.send(msg)
}
}
class WithSMS implements Notifier {
private wrapped: Notifier
constructor(wrapped: Notifier) {
this.wrapped = wrapped
}
send(msg: string): void {
this.wrapped.send(msg)
console.log("sms:", msg)
}
}
const notifier = new WithLog(new WithSMS(new EmailNotifier()))
notifier.send("server is down")
When to Use It
Use it to add optional, combinable behavior — logging, caching, retries, metrics — around a core you do not want to modify. Middleware in web servers is exactly this pattern.
Skip it when the behavior is not optional and not combinable — then it belongs inside the class itself, where it is easier to find.
Pros and Cons
Pros: behavior added without touching the core class; layers combine freely; each layer is small and testable alone.
Cons: a deep stack of wrappers is hard to debug — the call travels far before doing anything; construction lines get long.