AlgoMaster Logo

Bridge Design Pattern

Ashish

Ashish Pratap Singh

4 min read

The Bridge Design Pattern is a structural pattern that lets you decouple an abstraction from its implementation, allowing the two to vary independently.

It’s particularly useful in situations where:

  • You have classes that can be extended in multiple orthogonal dimensions (e.g., shape vs. rendering technology, UI control vs. platform).
  • You want to avoid a deep inheritance hierarchy that multiplies combinations of features.
  • You need to combine multiple variations of behavior or implementation at runtime.

The Bridge Pattern splits a class into two separate hierarchies:

  • One for the abstraction (e.g., shape, UI control)
  • One for the implementation (e.g., rendering engine, platform)

These two hierarchies are "bridged" via composition — not inheritance — allowing you to mix and match independently.

Let’s walk through a real-world example to see how we can apply the Bridge Pattern to build a system that is both flexible and scalable, without being buried under layers of rigid subclasses.

1. The Problem: Drawing Shapes

Imagine you're building a cross-platform graphics library. It supports rendering shapes like circles and rectangles using different rendering approaches:

  • 🟢 Vector rendering – for scalable, resolution-independent output
  • 🔵 Raster rendering – for pixel-based output

Now, you need to support:

  • Drawing different shapes (e.g., CircleRectangle)
  • Using different renderers (e.g., VectorRendererRasterRenderer)

Naive Implementation: Subclass for Every Combination

You might start by creating a class hierarchy that looks like this:

Circle Variants

Rectangle Variants

Client Code

Why This Quickly Breaks Down

1. Class Explosion

Every new combination of shape and rendering method requires a new subclass:

  • 2 shapes × 2 renderers = 4 classes
  • Add a third renderer (e.g., OpenGL)? Now you need 6 classes
  • Add more shapes (e.g., triangle, ellipse)? The combinations multiply

This makes the class hierarchy bloated and rigid.

2. Tight Coupling

Each class ties together shape logic and rendering logic. You can’t reuse rendering behavior independently of the shape — they’re intertwined.

3. Violates Open/Closed Principle

If you want to support a new rendering engine, you must modify or recreate every shape for that renderer.

What We Really Need

We need a solution that:

  • Separates the abstraction (Shape) from its implementation (Renderer)
  • Allows new renderers to be added without touching shape classes
  • Enables new shapes to be added without modifying or duplicating renderer logic
  • Keeps the system scalable, extensible, and composable

This is exactly where the Bridge Pattern comes in.

2. What is the Bridge Pattern

The Bridge Design Pattern lets you split a class into two separate hierarchies — one for the abstraction and another for the implementation — so that they can evolve independently.

In the Bridge Pattern, "abstraction has-a implementation" — the abstraction delegates work to an implementor object.

Class Diagram

1. Abstraction (e.g., Shape)

The high-level interface that defines the abstraction's core behavior. It maintains a reference to an Implementor and delegates work to it.

2. RefinedAbstraction (e.g., CircleRectangle)

A concrete subclass of Abstraction that adds additional behaviors or logic. It still relies on the implementor for actual execution.

3. Implementor (e.g., Renderer)

An interface that declares the operations to be implemented by concrete implementors. These are the low-level operations.

4. ConcreteImplementors (e.g., VectorRenderer,RasterRenderer)

Platform- or strategy-specific classes that implement the Implementor interface. They contain the actual logic for performing the delegated operations.

3. Implementing Bridge

Let’s now implement the Bridge Pattern to decouple our Shape abstraction (e.g., CircleRectangle) from the Renderer implementation (e.g., VectorRendererRasterRenderer).

This will allow us to mix and match shapes and rendering engines freely, without subclass explosion.

1: Define the Implementor Interface (Renderer)

This interface declares rendering operations for shapes. Concrete implementations will define how to render shapes using a particular technique (vector, raster, etc.).

2. Create Concrete Implementations of the Renderer

These classes provide platform-specific rendering logic.

🟢 VectorRenderer

🔵 RasterRenderer

3. Define the Abstraction (Shape)

This class holds a reference to the renderer and defines a general draw() method.

4. Create Concrete Shapes

Each shape delegates rendering to the renderer passed into it.

🟠 Circle

🟣 Rectangle

5. Client Code

Now we can freely combine shapes and rendering strategies — at runtime — without duplicating classes.

Output

What We Achieved

  • Decoupled abstractions from implementations: Shapes and renderers evolve independently
  • Open/Closed compliance: You can add new renderers or shapes without modifying existing ones
  • No class explosion: Avoided the need for every shape-renderer subclass
  • Runtime flexibility: Dynamically switch renderers based on user/device context
  • Clean, extensible design: Each class has a single responsibility and can be composed as needed