TypeScript Decorator Pattern Example

1. Definition

The Decorator pattern is a structural design pattern that lets you attach new behaviors to objects by placing them inside special wrapper objects. These wrappers are called decorators. This pattern is designed to promote code reusability and flexibility without modifying existing class structures.

2. Problem Statement

Sometimes, we want to add responsibilities to individual objects, not to an entire class. A straightforward way might be to extend the class and add new behaviors. However, it can be inflexible because it commits the class to a particular behavior and is done statically at compile-time.

3. Solution

Rather than statically extending the original class, the Decorator pattern proposes to add new behaviors dynamically by wrapping the original object inside a decorator object. Each decorator object mirrors the type of the decorated object but adds or overrides behavior.

4. Real-World Use Cases

1. Adding window dressing elements (like scroll bars, borders) in graphical windowing systems.

2. Extending the capabilities of libraries without modifying the actual library code.

3. Middleware in web servers to handle requests and responses.

5. Implementation Steps

1. Ensure a consistent interface between original objects and decorator objects. Usually, they implement the same interface or abstract class.

2. Create concrete decorator classes to add responsibilities to the original object.

3. Combine decorators by wrapping them around the original object.

6. Implementation in TypeScript

// Step 1: Create a consistent interface
interface Component {
    operation(): string;
}
// Concrete component providing default implementation
class ConcreteComponent implements Component {
    operation(): string {
        return 'ConcreteComponent';
    }
}
// Base decorator class
class Decorator implements Component {
    protected component: Component;
    constructor(component: Component) {
        this.component = component;
    }
    operation(): string {
        return this.component.operation();
    }
}
// Concrete decorators
class ConcreteDecoratorA extends Decorator {
    operation(): string {
        return `ConcreteDecoratorA(${super.operation()})`;
    }
}
class ConcreteDecoratorB extends Decorator {
    operation(): string {
        return `ConcreteDecoratorB(${super.operation()})`;
    }
}
// Usage:
const simpleComponent = new ConcreteComponent();
const decoratedComponentA = new ConcreteDecoratorA(simpleComponent);
const decoratedComponentB = new ConcreteDecoratorB(decoratedComponentA);
console.log(decoratedComponentB.operation());

Output:

ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))

Explanation:

The Decorator pattern allows us to add responsibilities to objects dynamically.

In the TypeScript example, the ConcreteComponent provides a default implementation. 

We then have a base Decorator class that can wrap around any Component. The concrete decorators, ConcreteDecoratorA and ConcreteDecoratorB, add or override behavior. By chaining decorators, we can compose various behaviors without altering the original classes.

7. When to use?

The Decorator pattern is useful when:

1. You need to add responsibilities to individual objects dynamically and transparently without affecting other objects.

2. Extending functionalities using inheritance is impractical because it results in a large number of subclasses.

3. Responsibilities can be withdrawn as well as added, allowing for flexible modifications.


Comments