053
GOF structural

Decorator

Decorator — class diagram
Plate 053 class diagram

The Decorator pattern enables us to change the behavior of an object by hiding the contained functions and adding new functionalities around it. It is also a common way to avoid the limits of single inheritance by multiple interface implementations (composition over inheritance).

Usage

This pattern finds its typical use in framework development because it enables the layering of functionalities around a core service.

Examples

  • Caching in HTTP services can be implemented as a decorator pattern by wrapping the http client within a client that analyzes the requested url and prepares a response
  • Logging and instrumenting methods can be implmented as a decorator that covers the interactions once for all the possible activities.

Specimens

15 implementations
Specimen 053.01 Dart View specimen ↗

The Decorator pattern dynamically adds responsibilities to an object. It provides a flexible alternative to subclassing for extending functionality. Instead of inheriting from a base class to add behavior, we wrap the object in decorator classes that add the desired functionality. This implementation uses Dart’s type system and composition to achieve this. The Component interface defines the core functionality, ConcreteComponent provides a basic implementation, and Decorator extends Component and holds a reference to another Component. Specific behaviors are added by ConcreteDecorator classes, which wrap the component and augment its behavior. This approach is idiomatic Dart as it favors composition over inheritance and leverages Dart’s flexible type system.

// Component interface
abstract class Component {
  String operation();
}

// Concrete Component
class ConcreteComponent implements Component {
  @override
  String operation() => 'Base operation';
}

// Decorator abstract class
abstract class Decorator implements Component {
  final Component _component;

  Decorator(this._component);

  @override
  String operation() => _component.operation();
}

// Concrete Decorator 1
class ConcreteDecoratorA extends Decorator {
  ConcreteDecoratorA(Component component) : super(component);

  @override
  String operation() {
    return 'Decorator A: ' + super.operation();
  }
}

// Concrete Decorator 2
class ConcreteDecoratorB extends Decorator {
  ConcreteDecoratorB(Component component) : super(component);

  @override
  String operation() {
    return 'Decorator B: ' + super.operation();
  }
}

void main() {
  Component component = ConcreteComponent();
  print(component.operation());

  Component decoratorA = ConcreteDecoratorA(component);
  print(decoratorA.operation());

  Component decoratorB = ConcreteDecoratorB(decoratorA);
  print(decoratorB.operation());
}