What is the Decorator design pattern, and why is it called a flexible alternative to subclassing for adding behavior to an object?
answer
- wrap, same interface, delegate inward
- N behaviors → N wrappers, not 2^N subclasses
- composition at runtime vs inheritance at compile time
- transparent to callers, stackable
- identity and instanceof break
basics
~20 sDecorator wraps an object inside another object that implements the same interface. The wrapper adds behavior before or after passing the call to the wrapped object. You add features by composing wrappers at runtime instead of writing subclasses.
solid answer
~50 sDecorator attaches additional responsibilities to an object dynamically. A decorator implements the same interface as the object it wraps, holds a reference to that object, and for every method does its extra work and then delegates inward. Because the wrapper's type equals the wrapped type, callers cannot tell the difference, so decorators can be stacked in any combination, e.g. caching(retrying(logging(realService))). Subclassing fixes the combination at compile time inside one hierarchy. With N optional behaviors you drift toward a combinatorial explosion of classes (LoggingCachingRetryingStore), while decorators need only N wrapper classes composed at wiring time. Decoration follows Open/Closed (add behavior without editing existing classes) and favors composition over inheritance, so wrappers can be added, removed, reordered, or enabled per environment. Costs: many small classes, deeper call stacks and stack traces, harder debugging, and identity surprises because the wrapper is not the same object as the wrapped one.
code
pseudocode · 25 linesinterface Store { String read(String key) }
class FileStore implements Store {
String read(String k) { return readFromDisk(k) }
}
class LoggingStore implements Store { // decorator
Store inner
LoggingStore(Store inner) { this.inner = inner }
String read(String k) {
log("read " + k)
return inner.read(k) // delegate
}
}
class CachingStore implements Store { // another decorator
Store inner; Map cache
String read(String k) {
if (cache.has(k)) return cache.get(k)
v = inner.read(k); cache.put(k, v); return v
}
}
// composed at wiring time, caller sees only Store
Store s = new CachingStore(new LoggingStore(new FileStore()))go deeper
Define it: a wrapper implementing the same interface, adds behavior, delegates inward; give one concrete example like adding logging around a data store.
Add the reason it beats subclassing (combinatorial explosion, runtime composition, Open/Closed) and name the roles: Component, ConcreteComponent, Decorator.
Discuss trade-offs: identity/instanceof breakage, self-calls not being intercepted, wrapping order as a semantic decision, and when a single class or an interceptor pipeline is simpler.
Frame it as a composition strategy for cross-cutting concerns: how wrappers are wired and ordered by the DI container, how the interface must be designed to stay decoratable, and when to move to middleware/AOP instead.
## The problem Suppose you have an object with a job: a `DataStore` with `read(key)` and `write(key, value)`. Later you want optional extras — log every call, cache reads, retry failures, encrypt values, record metrics. Each extra is orthogonal: any subset may be wanted, in different combinations, in different environments (retry in production, logging in dev, both in staging). **The subclassing approach.** Create `LoggingStore extends FileStore`, `CachingStore extends FileStore`, and so on. Two problems appear immediately: 1. **Combinatorial explosion.** To get logging *and* caching *and* retry, you need `LoggingCachingRetryingStore`. With N independent behaviors you may need up to 2^N classes, and each new base implementation (`S3Store`, `MemoryStore`) multiplies the count again. 2. **Static binding.** The combination is baked in at compile time. You cannot decide at startup, from configuration, that this deployment gets caching but not logging — unless you pre-build a class for each option. ## The Decorator solution Define the behavior as an **interface** (the *Component*): the contract callers depend on, e.g. `DataStore`. - **Concrete Component** — the real implementation that does the actual work (`FileStore`). - **Decorator** — a class that *also implements* `DataStore` and *holds a reference to another* `DataStore` (called the wrappee, inner, or delegate). Each method does something extra and then calls the same method on the inner object. That inward call is **delegation**. ``` class LoggingStore implements DataStore { private DataStore inner; LoggingStore(DataStore inner) { this.inner = inner; } String read(String key) { log("read " + key); return inner.read(key); // delegate } } ``` Because the decorator *is a* `DataStore`, it can be passed anywhere a `DataStore` is expected — including into another decorator. That is what makes wrappers **stackable**: ``` DataStore store = new CachingStore(new RetryingStore(new LoggingStore(new FileStore()))); ``` The caller holds a `DataStore` and is completely unaware of the layers. This property is called **interface preservation** or **transparency**: decoration must not change the contract, only enrich the behavior behind it. ## Why this is more flexible - **Runtime composition.** The stack is built by whoever wires the object graph (a factory, a dependency-injection container, a config file), so combinations are chosen per environment, per tenant, even per request. - **Linear class count.** N behaviors = N decorator classes, usable with any implementation of the interface, in any order. - **Open/Closed Principle.** New behavior arrives as a *new* class; existing tested classes are not edited. - **Single Responsibility.** Each wrapper holds exactly one concern (retry logic lives only in `RetryingStore`). - **Removable.** Deleting a feature means removing one line of wiring, not unpicking code from a class. ## Structural roles (GoF vocabulary) | Role | Meaning | |---|---| | Component | the shared interface/abstract type | | ConcreteComponent | the object being decorated; the "real" one | | Decorator | abstract base holding the reference and forwarding everything unchanged | | ConcreteDecorator | subclass that overrides selected methods to add behavior | The abstract `Decorator` base is a practical convenience: it forwards *all* methods verbatim so each concrete decorator only overrides what it cares about. It also localizes the damage when the interface gains a method — you add the pass-through once instead of in every wrapper. ## Trade-offs and edge cases - **Object identity.** `new LoggingStore(x) != x`. Code that compares references, uses the object as a map key, or relies on `equals`/`hashCode` semantics can misbehave; decorators usually should not try to fake identity. - **Type inspection breaks.** Runtime type checks and downcasts to the concrete class (`instanceof FileStore`) fail once wrapped. Client code that needs the concrete type is not decoration-friendly. - **Deep stacks.** Ten layers means ten frames in every stack trace and ten indirections per call; usually negligible, occasionally relevant in very hot paths. - **Order matters.** `retry(cache(x))` and `cache(retry(x))` behave differently. Wrapping order is a design decision, not an implementation detail. - **Self-calls are not decorated.** If the inner object calls its own method internally, that internal call goes straight to itself, bypassing the wrapper entirely — decoration only intercepts calls that cross the object boundary. - **Not for changing the interface.** If you need a *different* interface, that is Adapter. If the goal is to *control access* rather than add responsibility, that is Proxy. ## When subclassing is still fine One fixed variation, a behavior that genuinely needs access to protected internals, or a class not designed for extension by interface — those are legitimate reasons to inherit instead. Decorator pays off when behaviors are optional, orthogonal, and combinable.
- Why does a decorator have to implement the same interface as the object it wraps?So the wrapper is substitutable for the original everywhere the interface is used. That is what lets callers stay unaware of decoration and lets decorators wrap other decorators, which is what makes them stackable.
- Must a decorator always delegate to the inner object?Not always — a caching or short-circuiting decorator may skip the call. But it must honor the interface contract; a wrapper that silently changes semantics (e.g. returns null where the contract forbids it) breaks substitutability.
- How is Decorator different from Composite, which also holds references to the same interface?Composite holds a collection of children and models a part–whole tree, aggregating results. Decorator holds exactly one wrappee and adds responsibilities to it. Same recursive shape, different intent.
Layers of clothing. A base layer, then a fleece, then a rain shell — each layer wraps the previous one, adds one property (warmth, waterproofing), and you still look like a dressed person from the outside. You choose the combination each morning instead of owning one garment per weather combination.
saying these in an interview costs you the question
- "Decorator changes the object's interface" — that is Adapter; Decorator preserves it.
- "You decorate by subclassing the concrete class" — the wrapper implements the interface and holds a reference; it does not inherit the implementation.
- "Decorator modifies the original object" — it does not; it wraps it and the original is untouched.
- "Wrapping order is irrelevant since all wrappers get called" — order determines semantics.
- Confusing the pattern with language annotation syntax also called "decorators" (Python/TypeScript), which is a syntactic hook, not necessarily this pattern.
- "Decorator is just inheritance with extra steps" — it binds at runtime and composes; inheritance binds at compile time and does not combine.