The decorator pattern lets you add behaviour to an object by wrapping it in another object that looks exactly the same from the outside. You keep the original class untouched and stack small, single-purpose wrappers around it, choosing which ones to use while the program runs.
The idea in one sentence
A decorator is an object that implements the same interface as the thing it wraps, holds a reference to that thing, and passes every call through to it, doing a little extra work before or after.
Because the wrapper and the wrapped object share an interface, the code that uses them can't tell the difference. It asks for a notifier, a data stream or a request handler, and it gets one. Whether that object is the plain original or the original inside three layers of wrapping is invisible to the caller.
That gives you three useful properties:
- The core stays untouched. You add logging, formatting or extra checks without editing the original class, so its tests and its existing callers are safe.
- Each wrapper does one job. A logging decorator only logs. A validation decorator only validates. Each is small enough to read in one go.
- You combine them at runtime. The layers are ordinary objects, so you can decide which ones to apply based on configuration, the environment or user settings.
Why not just use subclasses?
The obvious way to add behaviour to a class is to subclass it. That works well for one feature. It falls apart when features are optional and can be combined.
Say an email notifier might need logging, a branded prefix on every message, and a check that rejects empty messages. With inheritance, every combination needs its own class: LoggingEmailNotifier, PrefixedEmailNotifier, LoggingPrefixedEmailNotifier, and so on. Three optional features give you eight combinations. Add an SMS notifier and you double it again. This is often called a 'class explosion', and it gets worse with every feature you add.
Inheritance also fixes the behaviour when you write the code. A LoggingEmailNotifier always logs. If you only want logging in development, you need extra conditions or yet another class.
Decorators swap that multiplication for addition. Three features mean three decorator classes, and any notifier can wear any combination of them. This is the open/closed principle in practice: the notifier is closed for modification but open for extension.
How the pattern is put together
There are four roles:
- The component: the shared interface or abstract class. Everything the client talks to has this type.
- The concrete component: the real object doing the core work, such as sending an email.
- The base decorator: implements the component interface and holds a reference to another component. By default it forwards every call to that inner object.
- Concrete decorators: subclasses of the base decorator that add one behaviour each, then call through to the object they wrap.
The part that trips people up is that a decorator both is a component and has a component. Here are those relationships as classes:
The 'is a' link is what lets a decorator stand in wherever the original was expected. The 'has a' link is what lets it pass the call on. Because a decorator can wrap any component, including another decorator, the wrappers stack as deep as you like.
A worked example in Python
Start with the component and the real object. Python's abc module gives us an abstract base class to play the role of the interface:
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, message: str) -> None: ...
class EmailNotifier(Notifier):
def send(self, message: str) -> None:
print(f"Email: {message}")Next, the base decorator. It stores the object it wraps and forwards calls to it, so concrete decorators only need to override what they change:
class NotifierDecorator(Notifier):
def __init__(self, wrapped: Notifier):
self._wrapped = wrapped
def send(self, message: str) -> None:
self._wrapped.send(message)Now three decorators, one for each kind of add-on: styling, logging and an extra check.
class PrefixNotifier(NotifierDecorator):
def send(self, message: str) -> None:
super().send(f"[Shop] {message}")
class LoggingNotifier(NotifierDecorator):
def send(self, message: str) -> None:
print(f"log: sending {len(message)} chars")
super().send(message)
class ValidatingNotifier(NotifierDecorator):
def send(self, message: str) -> None:
if not message.strip():
raise ValueError("empty message")
super().send(message)Finally, build the stack from the inside out and use it like any other notifier:
notifier = ValidatingNotifier(
LoggingNotifier(
PrefixNotifier(EmailNotifier())
)
)
notifier.send("Your order has shipped")Running it prints:
log: sending 22 chars
Email: [Shop] Your order has shippedFollow the call. ValidatingNotifier checks the message and passes it in. LoggingNotifier logs its length, 22 characters, and passes it in. PrefixNotifier adds the brand prefix. EmailNotifier finally sends it. None of those classes knows which other layers exist, and EmailNotifier never changed.
Because the stack is built from ordinary objects, it can depend on anything known at runtime. You could wrap in LoggingNotifier only when a debug setting is on, or skip the prefix for internal alerts, without writing a single new class.
Where you've already met it
Decorators are common in standard libraries, often without the name. In Java, new BufferedReader(new InputStreamReader(stream)) wraps a byte stream in a character reader, then wraps that in a buffer. In .NET, a GZipStream or BufferedStream wraps another Stream and is still a Stream. Middleware in web frameworks follows the same idea: each layer wraps the next handler and adds one concern, such as authentication, compression or logging.
Python's @decorator syntax is related, not the same
Python's @ decorators share the name and the spirit, but work differently. A function decorator such as @functools.lru_cache wraps a function when it is defined, and it applies to every call of that function. The design pattern wraps an object at runtime, so two instances of the same class can carry different wrappers. You can use Python's syntax to build decorator-style behaviour, but the two are separate ideas.
Common mistakes
Getting the order wrong. The order of the wrappers changes the result. If LoggingNotifier sat inside PrefixNotifier, it would log 29 characters, because the prefix had already been added. Order can also break correctness. Put the check inside the prefix and an empty message slips through:
broken = PrefixNotifier(
ValidatingNotifier(EmailNotifier())
)
broken.send(" ") # sends "[Shop] "By the time the validator sees it, the message is no longer blank. Decide the order deliberately, and build the stack in one place, such as a factory function, rather than scattering it around the codebase.
Forgetting to forward the call. A decorator that does its extra work but never calls the object it wraps silently swallows the behaviour underneath. A base decorator that forwards by default makes this much harder to get wrong.
Relying on the concrete type. Once an object is wrapped, isinstance(notifier, EmailNotifier) is False: the outer object is a ValidatingNotifier. Code that checks for a specific class, or reaches for a method only the concrete class has, stops working. Program against the shared interface instead.
Wrapping a wide interface. If the component has twenty methods, every decorator has to pass all twenty through. The base decorator absorbs most of that, but a wide interface is still a sign the pattern may be a poor fit.
When to use it
Reach for decorators when:
- you need optional behaviour that can be mixed and matched per object;
- you want to add behaviour to a class you can't or shouldn't edit, such as library code;
- each extra behaviour is a separate concern, like logging, caching, retries or validation.
Skip it when the behaviour is always needed, since it can simply live in the class, or when there is only ever one combination. A stack of wrappers also makes debugging harder: a stack trace shows several layers of send, and you need to know how the object was assembled to follow it.
It helps to tell it apart from its neighbours. An adapter changes an object's interface so it fits somewhere new; a decorator keeps the interface and adds behaviour. A proxy has the same shape as a decorator but controls access to the object, for example lazy loading or permission checks, rather than stacking features. A strategy swaps out the logic inside an object, where a decorator adds layers around the outside.
Key takeaways
- A decorator implements the same interface as the object it wraps, holds a reference to it, and forwards calls with extra work before or after.
- Decorators replace a subclass for every combination of features with one small class per feature, combined at runtime.
- The order of the wrappers matters, for both output and correctness.
- Code that uses a decorated object should depend on the interface, never on the concrete class inside.