Design Patterns Tutorial 0/70 lessons ~6 min read Lesson 26

    Decorator Pattern

    Decorator wraps an object that shares its interface and adds behaviour before/after delegating to the original.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Decorator wraps an object that shares its interface and adds behaviour before/after delegating to the original. Stack decorators at runtime: HttpClient wrapped by RetryClient, then LoggingClient, then MetricsClient — each adds one concern without subclassing the others.

    The story

    A team built four FileWriter subclasses: Buffered, Compressed, Encrypted, and EncryptedCompressedBuffered. Combinatorial explosion was visible. They refactored to stackable decorators — three orthogonal wrappers composing in any order.

    The business problem

    Optional behaviour combinations explode with inheritance:

    • Subclass explosion: retry × log × metric × cache = 2ⁿ classes.
    • Rigid composition: can't add logging without modifying base class.
    • Environment variance: test needs fakes; prod needs metrics — subclass tree can't flex.
    • Open/Closed violation: new concern means new subclass of every existing variant.

    The problem teams faced

    Decorator fits when:

    • Optional behaviours combine in many ways (retry × log × cache).
    • You need to add a concern without modifying the class or callers.
    • Composition must vary per environment (test, dev, prod) via config.
    • Same interface must be preserved — callers unaware of wrappers.

    Understanding the topic

    Intent: Attach additional responsibilities dynamically. Flexible alternative to subclassing.

    • Component — interface for objects that can be wrapped.
    • ConcreteComponent — base object doing core work.
    • Decorator — holds Component, forwards by default, adds pre/post behaviour.
    • Composition root — chains wrappers: Metrics(Logging(Retry(Base))).

    Internal architecture

    Stackable HTTP client decorators:

    ts
    interface HttpClient {
    fetch(url: string, opts?: RequestInit): Promise<Response>
    }
    class RetryDecorator implements HttpClient {
    constructor(private inner: HttpClient, private max = 3) {}
    async fetch(url: string, opts?: RequestInit) {
    for (let i = 0; i < this.max; i++) {
    try { return await this.inner.fetch(url, opts) }
    catch (e) { if (i === this.max - 1) throw e }
    }
    throw new Error("unreachable")
    }
    }
    // Composition root
    const client = new MetricsDecorator(
    new LoggingDecorator(
    new RetryDecorator(new FetchHttpClient())
    )
    )

    Visual explanation

    Three diagrams cover decorator chain, vs inheritance, and Express middleware:

    Decorator chain
    Client
    Uses Component IF
    MetricsDecorator
    pre/post timing
    LoggingDecorator
    log request
    ConcreteComponent
    core HTTP call
    Outermost decorator runs first on the way in, last on the way out.
    Decorator vs subclass explosion
    3 optional features
    2³ = 8 subclasses
    3 decorators
    3 classes + 1 base
    Add 4th feature
    +1 decorator
    Not +8 subclasses
    Open/Closed win
    Decorators compose; inheritance combines.
    Express middleware = Decorator
    Request
    Enters chain
    authMiddleware
    pre-check
    rateLimit
    pre-check
    handler
    core logic
    Higher-order functions wrapping handlers are Decorator without classes.

    Informative example

    Before / after — subclass explosion to decorator chain:

    ts
    // ❌ Combinatorial subclasses
    class EncryptedCompressedBufferedFileWriter extends FileWriter { /* ... */ }
    // ✅ Stackable decorators — any order, any subset
    interface Writer { write(data: Buffer): void }
    class BufferedWriter implements Writer {
    constructor(private inner: Writer) {}
    write(data: Buffer) { /* buffer then */ this.inner.write(data) }
    }
    class EncryptedWriter implements Writer {
    constructor(private inner: Writer) {}
    write(data: Buffer) { this.inner.write(encrypt(data)) }
    }
    const writer = new EncryptedWriter(new BufferedWriter(new FileWriter(path)))
    // Functional form (Express-style)
    const withLogging = (fn: Handler): Handler => (req, res, next) => {
    console.log(req.path); return fn(req, res, next)
    }

    Execution workflow

    1Decorator composition workflow
    1 / 5

    Define Component

    Interface with the operation clients call.

    HttpClient.fetch, Writer.write.

    Real-world use

    Java I/O streams (BufferedInputStream wrapping FileInputStream), Express/Koa middleware, Spring AOP advice chains, OkHttp interceptors, Python @decorator syntax, React HOCs, Java Servlet filters.

    Production case study

    HTTP client cross-cutting stack:

    • Scenario: 12 services each reimplemented retry, logging, metrics on outbound HTTP.
    • Decision: Shared decorator chain configured at composition root per service.
    • Outcome: New concern (circuit breaker) = one decorator class, zero service edits.
    • Metric: duplicated HTTP wrapper code eliminated across 12 repos.

    Trade-offs

    • Pro: compose features at runtime without subclass explosion.
    • Pro: Open/Closed — new decorator, no base class edits.
    • Con: deep chains hard to debug — order and nesting matter.
    • Con: many small wrapper objects — negligible unless extreme depth.

    Decision framework

    • Use when optional behaviours combine in many ways.
    • Use when same interface must be preserved for callers.
    • Avoid when single fixed behaviour — just implement it once.
    • Avoid when interface should change — that's Adapter, not Decorator.

    Best practices

    • One concern per decorator — retry, log, metrics are separate classes.
    • Document decorator order at composition root — outermost runs first.
    • Functional decorators (HOFs) in TS/JS when classes add no value.
    • Keep Component interface stable — decorators must not leak wrapper types.

    Anti-patterns to avoid

    • Mega-decorator doing retry + log + cache + auth in one class.
    • Decorator changing the interface — that's Adapter.
    • 20-layer chain with no documentation of order.

    Common mistakes

    • Wrong order — logging inside retry logs every retry attempt (may be intended or not).
    • Decorator holding mutable state shared across calls.
    • Double-wrapping same concern — metrics inside metrics.

    Debugging tips

    • Log decorator entry/exit with depth indent — trace chain execution.
    • Temporarily remove outer decorators to isolate which layer breaks.

    Optimization strategies

    • Skip expensive decorators in hot test paths.
    • Short-circuit decorators that noop when feature flag off.

    Common misconceptions

    • Decorator ≠ Proxy. Decorator adds behaviour; Proxy controls access/lazy-init with same role.
    • Express middleware is Decorator — no classes required.
    • Decorator preserves interface; Adapter changes it.

    Advanced interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    4 questions
    1IntermediateQuestionDecorator vs inheritance?+

    Answer

    Inheritance fixes composition at compile time — N features = 2^N subclasses. Decorators compose at runtime — N features = N decorator classes.

    Follow-up

    When inheritance OK?
    2AdvancedQuestionDecorator vs Proxy?+

    Answer

    Same interface both. Decorator adds responsibilities (log, retry). Proxy controls access (lazy init, remote stub, auth gate).

    Follow-up

    Spring @Transactional?
    3IntermediateQuestionImplement Decorator without classes?+

    Answer

    Higher-order functions. Express withLogging(handler) wraps and forwards — classic Decorator.

    Follow-up

    5-line example?
    4AdvancedQuestionDoes decorator order matter?+

    Answer

    Yes. Metrics outside retry measures total time including retries. Logging inside retry logs each attempt.

    Follow-up

    Which order for prod?

    Summary

    You can replace combinatorial subclasses with stackable decorators and explain order semantics. Tell the FileWriter explosion story — if you can do that, you own Decorator.

    Ready to mark this lesson complete?Track your journey across the entire course.