Bridge Pattern Implementation in TypeScript: A Comprehensive Guide

Learn how to implement the Bridge Pattern in TypeScript to separate abstraction from implementation using interfaces and classes.

5.2.1 Implementing Bridge in TypeScript

The Bridge Pattern is a structural design pattern that decouples an abstraction from its implementation, allowing both to vary independently. This pattern is particularly useful when you need to switch between different implementations at runtime without altering the client code. In this section, we will explore how to implement the Bridge Pattern in TypeScript, leveraging its strong typing and interface capabilities.

Understanding the Bridge Pattern

The Bridge Pattern involves four main components:

  1. Abstraction: Defines the abstraction’s interface and maintains a reference to an object of type Implementor.
  2. Implementor: Declares the interface for implementation classes.
  3. Concrete Implementor: Provides concrete implementations of the Implementor interface.
  4. Refined Abstraction: Extends the interface defined by Abstraction.

By separating the abstraction from the implementation, the Bridge Pattern allows you to change the implementation without modifying the abstraction or the client code.

Step-by-Step Implementation

Let’s walk through a step-by-step implementation of the Bridge Pattern in TypeScript.

Step 1: Define the Implementor Interface

The Implementor interface declares the methods that concrete implementors must provide. This interface acts as a bridge between the abstraction and the concrete implementations.

1// Implementor interface
2interface DrawingAPI {
3    drawCircle(x: number, y: number, radius: number): void;
4}

In this example, the DrawingAPI interface declares a method drawCircle, which concrete implementors will define.

Step 2: Create Concrete Implementor Classes

Concrete Implementor classes provide specific implementations of the methods declared in the Implementor interface.

 1// Concrete Implementor 1
 2class DrawingAPI1 implements DrawingAPI {
 3    drawCircle(x: number, y: number, radius: number): void {
 4        console.log(`API1.circle at (${x}, ${y}) with radius ${radius}`);
 5    }
 6}
 7
 8// Concrete Implementor 2
 9class DrawingAPI2 implements DrawingAPI {
10    drawCircle(x: number, y: number, radius: number): void {
11        console.log(`API2.circle at (${x}, ${y}) with radius ${radius}`);
12    }
13}

Here, DrawingAPI1 and DrawingAPI2 are two different implementations of the DrawingAPI interface.

Step 3: Define the Abstraction

The Abstraction class maintains a reference to an Implementor object and delegates the actual work to the Implementor.

 1// Abstraction
 2abstract class Shape {
 3    protected drawingAPI: DrawingAPI;
 4
 5    constructor(drawingAPI: DrawingAPI) {
 6        this.drawingAPI = drawingAPI;
 7    }
 8
 9    abstract draw(): void;
10    abstract resizeByPercentage(pct: number): void;
11}

The Shape class is an abstract class that holds a reference to a DrawingAPI object. It provides abstract methods draw and resizeByPercentage that must be implemented by subclasses.

Step 4: Create Refined Abstraction Classes

Refined Abstraction classes extend the interface defined by the Abstraction class.

 1// Refined Abstraction
 2class CircleShape extends Shape {
 3    private x: number;
 4    private y: number;
 5    private radius: number;
 6
 7    constructor(x: number, y: number, radius: number, drawingAPI: DrawingAPI) {
 8        super(drawingAPI);
 9        this.x = x;
10        this.y = y;
11        this.radius = radius;
12    }
13
14    draw(): void {
15        this.drawingAPI.drawCircle(this.x, this.y, this.radius);
16    }
17
18    resizeByPercentage(pct: number): void {
19        this.radius *= pct;
20    }
21}

The CircleShape class extends the Shape class and provides implementations for the draw and resizeByPercentage methods.

Step 5: Use the Bridge Pattern in Client Code

Now, let’s see how to use the Bridge Pattern in client code to switch implementations at runtime.

 1// Client code
 2const shapes: Shape[] = [
 3    new CircleShape(1, 2, 3, new DrawingAPI1()),
 4    new CircleShape(5, 7, 11, new DrawingAPI2())
 5];
 6
 7shapes.forEach(shape => {
 8    shape.resizeByPercentage(2.5);
 9    shape.draw();
10});

In the client code, we create an array of Shape objects, each using a different DrawingAPI implementation. We then iterate over the array, resizing and drawing each shape.

TypeScript-Specific Considerations

TypeScript’s strong typing and interface capabilities make it an excellent choice for implementing the Bridge Pattern. Here are some considerations:

  • Type Safety: TypeScript ensures that the methods declared in the Implementor interface are implemented by concrete classes, providing compile-time type safety.
  • Access Modifiers: Use access modifiers like protected to control access to the Implementor reference within the Abstraction class.
  • Constructor Injection: Inject the Implementor into the Abstraction through constructor injection, promoting loose coupling and flexibility.

Visualizing the Bridge Pattern

To better understand the Bridge Pattern, let’s visualize the relationships between the components using a class diagram.

    classDiagram
	    class DrawingAPI {
	        <<interface>>
	        +drawCircle(x: number, y: number, radius: number): void
	    }
	
	    class DrawingAPI1 {
	        +drawCircle(x: number, y: number, radius: number): void
	    }
	
	    class DrawingAPI2 {
	        +drawCircle(x: number, y: number, radius: number): void
	    }
	
	    class Shape {
	        #drawingAPI: DrawingAPI
	        +Shape(drawingAPI: DrawingAPI)
	        +draw(): void
	        +resizeByPercentage(pct: number): void
	    }
	
	    class CircleShape {
	        +CircleShape(x: number, y: number, radius: number, drawingAPI: DrawingAPI)
	        +draw(): void
	        +resizeByPercentage(pct: number): void
	    }
	
	    DrawingAPI <|.. DrawingAPI1
	    DrawingAPI <|.. DrawingAPI2
	    Shape <|-- CircleShape
	    Shape --> DrawingAPI

This diagram illustrates the relationships between the DrawingAPI, DrawingAPI1, DrawingAPI2, Shape, and CircleShape classes.

Try It Yourself

Now that we’ve covered the implementation of the Bridge Pattern in TypeScript, try modifying the code to add a new shape, such as a rectangle. Implement a new RectangleShape class that extends the Shape class and provides its own implementations for the draw and resizeByPercentage methods. Experiment with different DrawingAPI implementations and see how easily you can switch between them.

Key Takeaways

  • The Bridge Pattern decouples an abstraction from its implementation, allowing both to vary independently.
  • TypeScript’s interfaces and strong typing facilitate the implementation of the Bridge Pattern.
  • Constructor injection promotes loose coupling and flexibility.
  • The Bridge Pattern is useful when you need to switch between different implementations at runtime.

Further Reading

For more information on the Bridge Pattern, check out the following resources:

Quiz Time!

Loading quiz…

Remember, this is just the beginning. As you progress, you’ll build more complex and interactive applications using the Bridge Pattern and other design patterns. Keep experimenting, stay curious, and enjoy the journey!

Revised on Thursday, April 23, 2026