structural

Decorator

Wrap an object with other objects that implement the same contract so responsibilities can be layered dynamically.

Problem

structural

A system needs to add optional behaviors to an object without creating many subclasses for every feature combination.

Integration notes

Decorators work well for cross-cutting concerns such as logging, retry, caching, compression, and telemetry because each layer keeps the same interface while adding one concern.

Tradeoffs

  • Can introduce many small wrapper classes that are harder to trace in debugging.
  • Ordering of decorators matters, so composition needs to be deliberate.
  • Keeps responsibilities modular and easy to combine.

Languages

typescript, java, python, angular, react, react-native, csharp, dotnet

Platforms

frontend, backend, services

Variants

frontend

Stacked decorators

react

Layer several decorators in a known order so each wrapper adds one concern to the final result.

Intent: Combine multiple features by stacking wrappers.

Problem: Feature combinations become hard to manage if every permutation needs its own class.

Solution: Compose multiple decorators around the same base object.

Dependencies

decorator

Related variants

decorator-transparentdecorator-pipeline

Examples

decorator

Useful when layering UI styling, text formatting, or notification channels.

Scenarios

Text formatting

A text component can be wrapped with formatting decorators such as bold, italic, and underline without changing the original component.

Context: A document editor needs composable formatting behavior.

Problem: Many formatting combinations are awkward if each style is a separate subclass.

Solution: Use Decorator so text styling can be stacked dynamically.

frontend

Language examples

Core languages

Frameworks and libraries

Text formattingtypescript

Wrap text with formatting layers while preserving the same render contract.

TypeScript
interface TextNode {
  render(): string;
}

class PlainText implements TextNode {
  constructor(private text: string) {}
  render() {
    return this.text;
  }
}

class BoldText implements TextNode {
  constructor(private wrapped: TextNode) {}
  render() {
    return `<strong>${this.wrapped.render()}</strong>`;
  }
}

class ItalicText implements TextNode {
  constructor(private wrapped: TextNode) {}
  render() {
    return `<em>${this.wrapped.render()}</em>`;
  }
}

const text = new BoldText(new ItalicText(new PlainText("Hello")));
text.render();

Real-world examples

  • Service-layer cross-cutting behavior: Wrap application services with logging, retry, metrics, or authorization without rewriting the core service implementation.
  • HTTP client middleware: Add caching, telemetry, tracing, or circuit-breaking around a base client while preserving the same request interface.
  • I/O transformation pipelines: Layer compression and encryption around streams or file writers so data processing concerns remain modular.
  • Multi-channel notifications: Applications can combine in-app, email, SMS, and chat delivery by layering decorators around a base notifier.
  • Product customization: Pricing and description add-ons such as toppings, extras, or optional services can be composed dynamically.
  • UI and document formatting: Formatting or rendering behavior can be layered onto text and view components without changing their core implementation.