Bridge Pattern in TypeScript — ✨ Structural Design Pattern #2

365kim
3 min readMay 29, 2023

--

Photo by Modestas Urbonas on Unsplash

🙋🏻‍♀️ What is the Bridge Pattern?

The Bridge Pattern is a software design approach that creates a bridge between the two separate hierarchies and allows them to have multiple variations and evolve independently, increasing the flexibility of the system.

In a big system, adding a new feature will make the number of class combinations grow exponentially, requiring more classes. Then, the bridge can be a solution to deal with this problem by allowing classes to be divided into 2 hierarchies called abstraction and implementation.

  • abstraction: a high-level interface that references an implementation hierarchy.
  • implementation: low-level implementation details.

Now, the client can work with the abstraction without being tightly coupled to a specific implementation. This pattern favors object composition over class inheritance, following the principle “prefer composition over inheritance.”

✨ Simple TypeScript Example

Let’s say you have to build a design system that has many components and supports different themes. You are also expecting many changes in any theme details as well as components’ specifications. It’s time to utilize the Bridge Pattern!

Here, the Theme interface serves as the implementation side of the Bridge pattern. It declares the provideColorSet() method.

// Implementation
interface Theme {
provideColorSet(): void
}

// Concrete Implementation
class LightTheme implements Theme {
provideColorSet(): void {
console.log('Provide color set using light colors...')
}
}
class DarkTheme implements Theme {
provideColorSet(): void {
console.log('Provide color set using dark colors...')
}
}
class HighContrastTheme implements Theme {
provideColorSet(): void {
console.log('Provide color set using high contrast colors...')
}
}

The three different theme classes are concrete implementations of the Theme interface(LightTheme, DarkTheme, and HighContrastTheme.) Each class implements the provideColorSet() method with specific logic for providing color sets.

The Component interface represents the abstraction side of the Bridge pattern. It declares the render() method, which represents the high-level behavior that relies on the theme.

// Abstraction
interface Component {
render(): void
}

// Refined Abstraction
class Button implements Component {
private readonly theme: Theme

constructor(theme: Theme) {
this.theme = theme
}

render(): void {
this.theme.provideColorSet()
console.log('Drawing a button.')
}
}
class Dialog implements Component {
private readonly theme: Theme

constructor(theme: Theme) {
this.theme = theme
}

render(): void {
this.theme.provideColorSet()
console.log('Drawing a dialog.')
}
}
class Form implements Component {
private readonly theme: Theme

constructor(theme: Theme) {
this.theme = theme
}

render(): void {
this.theme.provideColorSet()
console.log('Drawing a form.')
}
}

The Button, Dialog, and Form classes are refined abstractions that implement the Component interface. Each refined abstraction depends on the Theme interface, which acts as a bridge to different concrete implementations of the theme. This separation allows for flexible composition and runtime selection of themes without modifying the components themselves.

On the client side, the instance of the concrete implementations of Theme, and the instances of the refined abstraction of Component is created. Then, the concrete theme implementation is passed as a parameter to the concrete refined abstraction.

// Client Code
const lightTheme: Theme = new LightTheme()
const lightButton: Component = new Button(lightTheme)

lightButton.render()

All combination of any element and any theme is possible, still easy to manage complexity and facilitate future modifications in the design system.

const darkTheme: Theme = new DarkTheme()
const darkDialog: Component = new Dialog(darkTheme)

darkDialog.render()

const highContrastTheme: Theme = new HighContrastTheme()
const highContrastForm: Component = new Form(highContrastTheme)

highContrastForm.render()

🧑🏻‍💻 Use it or Avoid it

When to use it

This is especially useful if there are multiple variations or dimensions of change and you anticipate different variations or extensions in both the abstraction and implementation hierarchies.

If you want the flexibility to switch implementations at runtime, this pattern can be a good choice.

When to avoid it

If you have a straightforward scenario with only one abstraction and one corresponding implementation, introducing the Bridge Pattern is unnecessary.

Also, if the abstraction and implementation are tightly coupled so that they are unlikely to change independently, usingthis pattern might introduce unnecessary complexity without clear benefits.

In short, the Bridge Pattern helps to manage complexity, improve flexibility by decoupling abstractions from their implementations.

--

--

365kim

Web Front-End Developer who believes Every Day Counts!