Decorator
Decorator is a structural pattern that adds behavior to an individual object by wrapping it in layers that share its interface, without touching the object's class or affecting other instances.
Problem
A coffee shop's ordering system starts with a `Coffee` class that has a `cost()` method. Customers can add milk, an extra shot, whipped cream, or any combination of the three. Modeling every combination as a subclass - `CoffeeWithMilk`, `CoffeeWithMilkAndShot`, `CoffeeWithMilkAndShotAndCream` - multiplies out of control as more add-ons are introduced, and most of those subclasses only differ by which extras they tack onto the price.
Worse, inheritance locks the combination in at class-definition time. If a customer wants to add whipped cream to an order that's already in progress, there's no subclass for "whatever this order already is, plus cream."
Solution
Give each add-on its own small wrapper class that implements the same interface as the base object and holds a reference to whatever it's wrapping. Calling `cost()` on a wrapper first asks the wrapped object for its cost, then adds its own charge on top.
Because every wrapper looks exactly like the object it wraps, wrappers stack: a `Coffee` can be wrapped in a `MilkDecorator`, then that result wrapped again in a `WhipDecorator`, and the outermost object still answers to `cost()` and any other shared method. Combinations are assembled at runtime by choosing which wrappers to apply, instead of being baked into a fixed subclass.
When to Use
- You need to attach extra behavior to specific object instances at runtime, while leaving every other instance of the same class untouched.
- Subclassing would require one class per combination of optional features, and that number is only going to grow.
- Different objects need different mixes of the same set of add-on behaviors, decided at runtime rather than fixed in advance.
Real-World Examples
- **Java I/O streams** - `BufferedInputStream` and `GZIPInputStream` wrap a raw `InputStream` to add buffering or decompression without changing the underlying stream class.
- **Python function decorators** - `@lru_cache` and `@retry` wrap a function with caching or retry logic while leaving the original function definition untouched.
- **Express/Koa middleware chains** - each middleware wraps the next handler, layering on logging, authentication, or compression without modifying the route logic itself.
Structure
A single interface covers the object being extended and every wrapper around it. A base decorator class holds a reference to whatever it wraps and simply forwards calls by default; concrete decorators override that forwarding to inject their own behavior before or after the call reaches the wrapped object.
Participants
The interface every layer must satisfy - both the original object and every decorator around it. In the diagram, that's `Notifier` and its send() method.
The plain, undecorated object at the center of the stack, carrying the original behavior the wrappers will build on - the Email Notifier in the diagram.
A shared parent for all decorators that stores the wrapped Component and forwards calls to it, so each concrete decorator only needs to add its own piece.
One extra behavior layered on top - calls into the wrapped object first, then performs its own step. The SMS and Slack decorators in the diagram are two such layers.
Assembles the desired stack of decorators around the base object, then calls the outermost layer through the shared Component interface - a single send() call in the diagram.
class Notifier {
send(message) {
throw new Error('send() must be implemented');
}
}
class EmailNotifier extends Notifier {
constructor(email) {
super();
this.email = email;
}
send(message) {
console.log(`Email to ${this.email}: ${message}`);
}
}
class NotifierDecorator extends Notifier {
#wrapped;
constructor(notifier) {
super();
this.#wrapped = notifier;
}
send(message) {
this.#wrapped.send(message);
}
}
class SMSDecorator extends NotifierDecorator {
constructor(notifier, phone) {
super(notifier);
this.phone = phone;
}
send(message) {
super.send(message);
console.log(`SMS to ${this.phone}: ${message}`);
}
}
class SlackDecorator extends NotifierDecorator {
constructor(notifier, channel) {
super(notifier);
this.channel = channel;
}
send(message) {
super.send(message);
console.log(`Slack #${this.channel}: ${message}`);
}
}
let notifier = new EmailNotifier('user@example.com');
notifier = new SMSDecorator(notifier, '+1234567890');
notifier = new SlackDecorator(notifier, 'alerts');
notifier.send('Server is down!');
Step 1 of 6
Notifier is the common component interface
`send()` just throws - both the base notifier and every decorator around it must implement this one method.
Step 2 of 6
EmailNotifier is the concrete component being wrapped
It implements `send()` directly, with no wrapping - this is the base object decorators will add behavior around.
Step 3 of 6
NotifierDecorator wraps a Notifier and forwards by default
It also `extends Notifier`, stores the wrapped instance in `#wrapped`, and its own `send()` just calls `this.#wrapped.send(message)` - a decorator is itself a `Notifier`, so decorators can be stacked.
Step 4 of 6
SMSDecorator adds behavior after delegating
Its `send()` calls `super.send(message)` first (running whatever's wrapped), then adds its own `console.log` for the SMS - extending behavior without modifying the wrapped object.
Step 5 of 6
SlackDecorator does the same, independently
It follows the identical `super.send()` then extra `console.log` shape as `SMSDecorator` - each decorator only knows about adding its own behavior, not about the others.
Step 6 of 6
Decorators are stacked at runtime, one call at a time
`notifier` is reassigned twice - first wrapped in `SMSDecorator`, then in `SlackDecorator` - so `notifier.send(...)` ends up triggering email, SMS, and Slack, in that nested order, without changing `EmailNotifier` at all.
interface Notifier {
send(message: string): void;
}
class EmailNotifier implements Notifier {
constructor(private email: string) {}
public send(message: string): void {
console.log(`Email to ${this.email}: ${message}`);
}
}
abstract class NotifierDecorator implements Notifier {
constructor(protected wrapped: Notifier) {}
public send(message: string): void {
this.wrapped.send(message);
}
}
class SMSDecorator extends NotifierDecorator {
constructor(notifier: Notifier, private phone: string) {
super(notifier);
}
public send(message: string): void {
super.send(message);
console.log(`SMS to ${this.phone}: ${message}`);
}
}
class SlackDecorator extends NotifierDecorator {
constructor(notifier: Notifier, private channel: string) {
super(notifier);
}
public send(message: string): void {
super.send(message);
console.log(`Slack #${this.channel}: ${message}`);
}
}
let notifier: Notifier = new EmailNotifier('user@example.com');
notifier = new SMSDecorator(notifier, '+1234567890');
notifier = new SlackDecorator(notifier, 'alerts');
notifier.send('Server is down!');
interface Notifier { void send(String message); }
class EmailNotifier implements Notifier {
private final String email;
EmailNotifier(String email) { this.email = email; }
public void send(String message) {
System.out.println("Email to " + email + ": " + message);
}
}
abstract class NotifierDecorator implements Notifier {
protected final Notifier wrapped;
NotifierDecorator(Notifier notifier) { this.wrapped = notifier; }
public void send(String message) { wrapped.send(message); }
}
class SMSDecorator extends NotifierDecorator {
private final String phone;
SMSDecorator(Notifier n, String phone) { super(n); this.phone = phone; }
public void send(String message) {
super.send(message);
System.out.println("SMS to " + phone + ": " + message);
}
}
class SlackDecorator extends NotifierDecorator {
private final String channel;
SlackDecorator(Notifier n, String channel) { super(n); this.channel = channel; }
public void send(String message) {
super.send(message);
System.out.println("Slack #" + channel + ": " + message);
}
}
Notifier notifier = new EmailNotifier("user@example.com");
notifier = new SMSDecorator(notifier, "+1234567890");
notifier = new SlackDecorator(notifier, "alerts");
notifier.send("Server is down!");
interface INotifier { void Send(string message); }
class EmailNotifier : INotifier
{
private readonly string _email;
public EmailNotifier(string email) => _email = email;
public void Send(string message) =>
Console.WriteLine($"Email to {_email}: {message}");
}
abstract class NotifierDecorator : INotifier
{
protected readonly INotifier Wrapped;
protected NotifierDecorator(INotifier notifier) => Wrapped = notifier;
public virtual void Send(string message) => Wrapped.Send(message);
}
class SMSDecorator : NotifierDecorator
{
private readonly string _phone;
public SMSDecorator(INotifier n, string phone) : base(n) => _phone = phone;
public override void Send(string message)
{ base.Send(message); Console.WriteLine($"SMS to {_phone}: {message}"); }
}
class SlackDecorator : NotifierDecorator
{
private readonly string _channel;
public SlackDecorator(INotifier n, string channel) : base(n) => _channel = channel;
public override void Send(string message)
{ base.Send(message); Console.WriteLine($"Slack #{_channel}: {message}"); }
}
INotifier notifier = new EmailNotifier("user@example.com");
notifier = new SMSDecorator(notifier, "+1234567890");
notifier = new SlackDecorator(notifier, "alerts");
notifier.Send("Server is down!");
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, message: str) -> None:
pass
class EmailNotifier(Notifier):
def __init__(self, email: str) -> None:
self._email = email
def send(self, message: str) -> None:
print(f"Email to {self._email}: {message}")
class NotifierDecorator(Notifier):
def __init__(self, wrapped: Notifier) -> None:
self._wrapped = wrapped
def send(self, message: str) -> None:
self._wrapped.send(message)
class SMSDecorator(NotifierDecorator):
def __init__(self, notifier: Notifier, phone: str) -> None:
super().__init__(notifier)
self._phone = phone
def send(self, message: str) -> None:
super().send(message)
print(f"SMS to {self._phone}: {message}")
class SlackDecorator(NotifierDecorator):
def __init__(self, notifier: Notifier, channel: str) -> None:
super().__init__(notifier)
self._channel = channel
def send(self, message: str) -> None:
super().send(message)
print(f"Slack #{self._channel}: {message}")
notifier: Notifier = EmailNotifier("user@example.com")
notifier = SMSDecorator(notifier, "+1234567890")
notifier = SlackDecorator(notifier, "alerts")
notifier.send("Server is down!")
Step 1 of 6
Notifier is the common component interface
`send()` is `@abstractmethod` - both the base notifier and every decorator must implement this same method to be usable.
Step 2 of 6
EmailNotifier is the concrete component being wrapped
It implements `send()` directly with a `print` call - this is the base object decorators will add behavior around.
Step 3 of 6
NotifierDecorator wraps a Notifier and forwards by default
It also subclasses `Notifier`, stores the wrapped instance as `self._wrapped`, and its `send()` just calls `self._wrapped.send(message)` - being a `Notifier` itself is what lets decorators stack.
Step 4 of 6
SMSDecorator adds behavior after delegating
`send()` calls `super().send(message)` first, then prints its own SMS line - the wrapped object's behavior always runs, plus something extra.
Step 5 of 6
SlackDecorator does the same, independently
It follows the same `super().send()` then extra `print` shape as `SMSDecorator` - neither decorator knows about the other.
Step 6 of 6
Decorators are stacked at runtime, one call at a time
`notifier` is reassigned first to `SMSDecorator(notifier, ...)`, then to `SlackDecorator(notifier, ...)` - `notifier.send(...)` ends up running email, SMS, and Slack behavior in that nested order, with `EmailNotifier` untouched.
Click "Run" to execute this code in a sandboxed frame and see console output here.
TypeScript runs as plain JavaScript here - type annotations are stripped, not type-checked.
Click "Run" to execute this code in a sandboxed frame and see console output here.
Advantages
- New optional behavior becomes a small independent wrapper class instead of another branch in an ever-growing subclass tree.
- Behavior can be attached to or removed from one specific object at runtime, without touching the class definition or any other instance.
- Each wrapper stays focused on a single concern, so responsibilities don't pile up inside one bloated class.
- Adding a new kind of wrapper never requires touching the base object or any decorator already shipped.
Disadvantages
- Pulling one specific wrapper out of the middle of an existing stack usually means rebuilding the stack, since each layer only knows about its immediate neighbor.
- When decorators aren't commutative, the order they're applied in changes the result, which makes assembling the stack error-prone.
- A deeply nested stack of wrappers can be tedious to step through in a debugger compared to one flat class.
Question 1 of 10
According to the pattern's stated drawbacks, what happens when decorators aren't commutative?
Correct! When decorators aren't commutative, the order they're applied in changes the result, which makes assembling the stack error-prone.
Not quite. When decorators aren't commutative, the order they're applied in changes the result, which makes assembling the stack error-prone.
Question 2 of 10
In the walkthrough, what does SMSDecorator's `send()` do, in order?
Correct! SMSDecorator's send() calls super.send(message) first, running whatever is wrapped, then adds its own console.log for the SMS - extending behavior without modifying the wrapped object.
Not quite. SMSDecorator's send() calls super.send(message) first, running whatever is wrapped, then adds its own console.log for the SMS - extending behavior without modifying the wrapped object.
Question 3 of 10
What goes wrong modeling every coffee add-on combination (milk, shot, cream) as its own subclass?
Correct! Modeling every combination as a subclass multiplies out of control, and inheritance locks the combination in at class-definition time - there's no subclass for adding cream to an order already in progress.
Not quite. Modeling every combination as a subclass multiplies out of control, and inheritance locks the combination in at class-definition time - there's no subclass for adding cream to an order already in progress.
Question 4 of 10
Given `notifier = new SMSDecorator(notifier, ...)` followed by `notifier = new SlackDecorator(notifier, ...)`, in what order does a single `notifier.send(...)` call trigger the three channels?
Correct! notifier ends up wrapped as SlackDecorator(SMSDecorator(EmailNotifier)), and since each decorator delegates inward before adding its own step, send() triggers email, SMS, and Slack in that nested order.
Not quite. notifier ends up wrapped as SlackDecorator(SMSDecorator(EmailNotifier)), and since each decorator delegates inward before adding its own step, send() triggers email, SMS, and Slack in that nested order.
Question 5 of 10
What is a stated drawback of pulling one specific wrapper out of the middle of an existing decorator stack?
Correct! Pulling one wrapper out of the middle of a stack usually means rebuilding it, since each layer only knows about its immediate neighbor.
Not quite. Pulling one wrapper out of the middle of a stack usually means rebuilding it, since each layer only knows about its immediate neighbor.
Question 6 of 10
What does Decorator add to an object, and how?
Correct! Decorator adds behavior to an individual object by wrapping it in layers that share its interface, without touching the object's class or affecting other instances.
Not quite. Decorator adds behavior to an individual object by wrapping it in layers that share its interface, without touching the object's class or affecting other instances.
Question 7 of 10
How does calling `cost()` on a decorator wrapper work?
Correct! Calling cost() on a wrapper first asks the wrapped object for its cost, then adds its own charge on top - that's how wrappers stack.
Not quite. Calling cost() on a wrapper first asks the wrapped object for its cost, then adds its own charge on top - that's how wrappers stack.
Question 8 of 10
In the Notifier structure, what role does the Base Decorator (NotifierDecorator) play?
Correct! The Base Decorator is a shared parent for all decorators that stores the wrapped Component and forwards calls to it, so each concrete decorator only needs to add its own piece.
Not quite. The Base Decorator is a shared parent for all decorators that stores the wrapped Component and forwards calls to it, so each concrete decorator only needs to add its own piece.
Question 9 of 10
How do Java's `BufferedInputStream` and `GZIPInputStream` illustrate Decorator, per the real-world examples?
Correct! BufferedInputStream and GZIPInputStream wrap a raw InputStream to add buffering or decompression without changing the underlying stream class - the same wrap-without-modifying idea as the Notifier decorators.
Not quite. BufferedInputStream and GZIPInputStream wrap a raw InputStream to add buffering or decompression without changing the underlying stream class - the same wrap-without-modifying idea as the Notifier decorators.
Question 10 of 10
Why must both EmailNotifier and every decorator implement the same `Notifier` interface?
Correct! NotifierDecorator also extends Notifier, so a decorator is itself a Notifier - that shared interface is exactly what lets decorators be stacked on top of each other.
Not quite. NotifierDecorator also extends Notifier, so a decorator is itself a Notifier - that shared interface is exactly what lets decorators be stacked on top of each other.