Skip to content
Commitwise
Menu

When would you use the Decorator pattern instead of inheritance?

Design patternsMid-level

In shortWhen behaviors like caching, logging, retries or metrics should be combined freely around an object without changing it. Decorators wrap the same interface and compose; inheritance needs a subclass per combination and is fragile when the base class calls its own methods.

Model answer

Inheritance adds behavior at compile time, one combination per class: CachedRepository, LoggingCachedRepository, RetryingLoggingCachedRepository… A decorator implements the same interface as the object it wraps, adds one behavior, and delegates to the inner object, so behaviors compose at runtime:

val repo: ProductRepository = Logging(Caching(Retrying(ApiProductRepository(api))))

Use decorators when:

  • several cross-cutting behaviors should combine freely (cache, log, retry, metrics, auth),
  • you can’t or shouldn’t modify the original (third-party, final, shared),
  • you want each behavior testable alone with a fake inner object.

Things to get right:

  • Order is semantics: a cache outside a logger hides hits from the log; a retry outside a timeout gives each attempt its own timeout.
  • Every method is wrapped: a generic retry around an interface that includes charge() can double-charge on timeouts.
  • Composition over inheritance: subclassing a concrete class to “decorate” it breaks when the base class calls its own overridable methods (HashSet.addAll calling add), the fragile base class problem. Kotlin’s by delegation avoids it but forwards un-overridden methods straight to the inner object.

What they're looking for

The combinatorial-subclass problem, how decorators delegate and compose, real examples (Java I/O, OkHttp interceptors, SwiftUI modifiers, Spring proxies), and the subtleties of ordering and of non-idempotent operations. Bonus for the fragile base class problem.

Common mistakes

  • Describing a decorator that doesn't share the wrapped object's interface.
  • Ignoring that wrapper order changes behavior.
  • Wrapping a payment API in a generic retry decorator.

More design patterns questions

Esc

Type to search the whole site.