TypeScript Bridge Pattern Example

1. Definition

The Bridge pattern is a structural design pattern that decouples an abstraction from its implementation. It enables the two to vary independently, such that both can be extended without affecting each other.

2. Problem Statement

When an abstraction and its implementations are tightly bound in a monolithic class structure, changes to one directly affect the other. This tight coupling makes it challenging to scale, extend, or modify parts of the system independently.

3. Solution

The Bridge pattern splits the monolithic structure into two hierarchies - abstraction and implementation. The abstraction contains a reference to the implementation. This decoupling allows you to extend and modify the abstraction and implementation hierarchies independently.

4. Real-World Use Cases

1. UI and operating system interactions in cross-platform toolkits.

2. Switching between different data storage or database implementations.

3. Adapting different drawing/rendering engines in graphics software.

5. Implementation Steps

1. Identify the dimensions in which the system needs to vary: the abstraction and the implementation.

2. Separate them into different class hierarchies.

3. Have a bridge (reference) in the abstraction hierarchy that points to an instance in the implementation hierarchy.

6. Implementation in TypeScript

// Step 1: Separate abstraction and implementation
// Implementation base interface
interface Implementation {
    operationImplementation(): string;
}
// Concrete implementations
class ConcreteImplementationA implements Implementation {
    operationImplementation(): string {
        return "ConcreteImplementationA";
    }
}
class ConcreteImplementationB implements Implementation {
    operationImplementation(): string {
        return "ConcreteImplementationB";
    }
}
// Abstraction base class with a bridge to implementation
class Abstraction {
    protected implementation: Implementation;
    constructor(implementation: Implementation) {
        this.implementation = implementation;
    }
    operation(): string {
        return `Abstraction on ${this.implementation.operationImplementation()}`;
    }
}
// Extended Abstraction
class ExtendedAbstraction extends Abstraction {
    operation(): string {
        return `ExtendedAbstraction on ${this.implementation.operationImplementation()}`;
    }
}
// Usage:
const implementationA = new ConcreteImplementationA();
const abstraction1 = new Abstraction(implementationA);
console.log(abstraction1.operation());
const implementationB = new ConcreteImplementationB();
const abstraction2 = new ExtendedAbstraction(implementationB);
console.log(abstraction2.operation());

Output:

Abstraction on ConcreteImplementationA
ExtendedAbstraction on ConcreteImplementationB

Explanation:

The Bridge pattern decouples an abstraction from its implementation, allowing both to vary independently. 

In our TypeScript example, the Abstraction class acts as the bridge to the Implementation. This bridge allows us to change or extend the abstraction (e.g., ExtendedAbstraction) and implementation (e.g., ConcreteImplementationA and ConcreteImplementationB) independently.

7. When to use?

The Bridge pattern is useful when:

1. You want to avoid permanent binding between an abstraction and its implementation.

2. Both the abstractions and their implementations should be extensible independently without affecting each other.

3. Changes in the implementation of an abstraction should have no impact on clients; that is, their code should not need to be recompiled.

4. You want to hide the implementation of an abstraction completely from clients.


Comments