Introduction

Sometimes a single concept in your system varies along two different dimensions:

  • Shapes: Circle, Square, Rectangle

  • Rendering APIs: OpenGL, DirectX, Vulkan

If you model this with plain inheritance, you might end up with classes like:

  • OpenGLCircle, OpenGLSquare, DirectXCircle, DirectXSquare, and so on.

This leads to a class explosion: every new shape × every new rendering API = a new subclass.

The Bridge Design Pattern solves this by separating the abstraction from its implementation, letting them evolve independently and be combined flexibly at runtime.


Intent of the Bridge pattern

The core intent:

Decouple an abstraction from its implementation so that the two can vary independently.

Key ideas:

  • Abstraction: the high‑level concept the client uses (e.g., Shape).

  • Implementor: the low‑level detail that actually does the work (e.g., Renderer).

  • The abstraction has a reference to an implementor (composition), instead of inheriting from it.

  • You can extend abstraction and implementation hierarchies separately without creating a subclass for every combination.

Bridge is about two orthogonal dimensions of variation, not just adapting mismatched interfaces as in Adapter.


Basic structure: Abstraction and Implementor

Conceptually:

  • Abstraction (e.g., Shape)

    • Holds a pointer/reference to Implementor (e.g., Renderer).

    • Defines high‑level operations that delegate “how” to the implementor.

  • RefinedAbstraction

    • Concrete specializations of the abstraction (e.g., Circle, Rectangle).

  • Implementor

    • Interface for low‑level operations (e.g., drawCircle, drawRectangle).

  • ConcreteImplementor

    • Concrete implementations (e.g., OpenGLRenderer, DirectXRenderer).


C++ example: Shapes and Renderers

Step 1: Implementor hierarchy (how to draw)


Concrete implementors:


Step 2: Abstraction hierarchy (what it is)


Concrete abstraction:


Step 3: Using the bridge


Now:

  • Shape hierarchy varies independently (e.g., later add Rectangle, Triangle).

  • Renderer hierarchy also varies independently (e.g., add VulkanRenderer).

  • No need for OpenGLCircle, DirectXCircle, VulkanCircle, etc. You just combine Circle with whichever Renderer you need.


Why use Bridge instead of simple inheritance?

Without Bridge, a naive design might be:

  • class OpenGLCircle

  • class DirectXCircle

  • class OpenGLRectangle

  • class DirectXRectangle

Problems:

  • Adding a new shape requires implementing it for every renderer.

  • Adding a new renderer requires implementing it for every shape.

  • The number of classes grows as a product of the two dimensions.

With Bridge:

  • You have one Circle class that works with any Renderer.

  • To support a new renderer, just add one ConcreteImplementor (e.g., VulkanRenderer).

  • To support a new shape, add one RefinedAbstraction (e.g., Rectangle) that uses the existing Renderer interface.

Bridge is especially helpful when:

  • You know in advance there are two independent axes of change.

  • Both axes are likely to grow over time.


Bridge vs Adapter

Bridge and Adapter can look similar (both use composition and interfaces), but their intent differs:

  • Adapter

    • Goal: Make two existing interfaces compatible without changing them.

    • Typically used to integrate legacy or third‑party code.

    • Often applied after the fact.

  • Bridge

    • Goal: Design a system with two independent dimensions of variation from the start.

    • Used proactively in your design to avoid class explosion.

    • Both abstraction and implementor are under your control.

A mental shortcut:

  • If you’re trying to plug in an incompatible library, think Adapter.

  • If you’re trying to separate your own abstraction from its varying implementations, think Bridge.


Another example: Remote control and devices

Imagine:

  • Abstraction hierarchy: RemoteControl, AdvancedRemoteControl.

  • Implementor hierarchy: TV, Radio.

With Bridge:

  • RemoteControl holds a Device& and forwards operations to it.

  • You can pair any remote with any device at runtime: TV with AdvancedRemoteControl, Radio with simple RemoteControl, etc.

  • Adding a SmartTV or Speaker doesn’t require new remote subclasses.

This fits many Low Level Design problems where you have:

  • “Controller” vs “Controlled device”

  • “View” vs “Platform‑specific windowing system”

  • “Abstraction of operation” vs “Concrete backend implementation”


When to use the Bridge pattern (and when not to)

Use Bridge when:

  • A class has two independent reasons to change (two dimensions of variation).

  • You want to avoid a combinatorial explosion of subclasses.

  • You foresee adding more variations in both axes over time.

  • You want to choose or swap implementations at runtime (e.g., select renderer based on configuration).

Consider skipping Bridge when:

  • There is only one implementation and no real sign of multiple axes of variation.

  • The hierarchy is small and simple; Bridge might over‑engineer the solution.

  • You are actually solving an integration problem (then Adapter might be enough).

As always, apply KISS and YAGNI:

  • Start simple.

  • Introduce Bridge when both abstraction and implementation clearly need to vary independently.


Summary

The Bridge Design Pattern is a structural pattern that:

  • Separates an abstraction (e.g., Shape, RemoteControl) from its implementation (e.g., Renderer, Device).

  • Connects them via composition, allowing each side to have its own hierarchy.

  • Prevents class explosion when there are two independent dimensions of variation.

  • Differs from Adapter by being a proactive design choice for flexibility, not just a compatibility fix.

You saw:

  • A C++ example where Shape uses a Renderer to draw circles.

  • How adding new shapes or new renderers doesn’t multiply the number of classes.

  • How Bridge compares with Adapter and when to use each.