Bridge

Learning Objectives

Separate an abstraction from its implementation so both vary independently.

Avoid class explosion from two dimensions of variation.

Distinguish Bridge from Adapter.

Prerequisites: Modules 7, 33.

Intuition and Definition

Intent: decouple an abstraction from its implementation so that the two can vary independently.

Problem: two dimensions of variation — e.g. *what* is displayed (gauge, warning, menu) × *where* it is rendered (LCD, head-up display, test console). Inheritance alone gives |shapes| × |renderers| classes.

Participants: Abstraction (holds a reference to an Implementor), RefinedAbstractions, the Implementor interface, ConcreteImplementors.

Why It Matters

Bridge turns an N×M class hierarchy into N + M classes and lets you pick the implementation at run time.

Syntax and Language Notes

The C++ Pimpl idiom ("pointer to implementation") is a special case of Bridge used to hide implementation details and keep ABI stable (Module 63).

Complete Example

#include <iostream>
#include <memory>
#include <string>

struct Renderer {                                        // Implementor
    virtual ~Renderer() = default;
    virtual void text(int x, int y, const std::string& s) = 0;
    virtual void bar(int x, int y, int percent) = 0;
};
struct LcdRenderer : Renderer {
    void text(int x, int y, const std::string& s) override { std::cout << "  LCD text@" << x << "," << y << ": " << s << "\n"; }
    void bar(int x, int y, int p) override { std::cout << "  LCD bar@" << x << "," << y << ": " << std::string(static_cast<size_t>(p / 10), '#') << "\n"; }
};
struct HudRenderer : Renderer {
    void text(int, int, const std::string& s) override { std::cout << "  HUD projects: " << s << "\n"; }
    void bar(int, int, int p) override { std::cout << "  HUD arc " << p << "%\n"; }
};

class Widget {                                           // Abstraction
protected:
    Renderer& r_;
public:
    explicit Widget(Renderer& r) : r_(r) {}
    virtual ~Widget() = default;
    virtual void draw() = 0;
};
class FuelGauge : public Widget {                        // Refined abstractions
    int percent_;
public:
    FuelGauge(Renderer& r, int p) : Widget(r), 
…
LCD text@0,0: Fuel
  LCD bar@0,1: ####
  LCD text@0,5: ! Tyre pressure
  HUD projects: Fuel
  HUD arc 40%
  HUD projects: ! Tyre pressure

Step-by-Step Walkthrough

Two widgets × two renderers = 4 combinations from 4 classes (not 4 subclasses like LcdFuelGauge, HudFuelGauge, …).

Adding a third renderer (e.g. a test console) is one class; every widget works on it immediately.

The widget hierarchy and renderer hierarchy evolve independently.

classDiagram
  class Widget {
    <<abstract>>
    +draw()
  }
  class Renderer {
    <<interface>>
    +text(x, y, s)
    +bar(x, y, p)
  }
  Widget o-- Renderer : implementor
  Widget <|-- FuelGauge
  Widget <|-- WarningBanner
  Renderer <|.. LcdRenderer
  Renderer <|.. HudRenderer

Common Mistakes

Bridge for one dimension of variation — plain polymorphism suffices.

Implementor interfaces mirroring one implementation (e.g. LCD-specific calls) — then other renderers can't implement them.

Trade-offs and Alternatives

Bridge is designed up front to let two hierarchies vary; Adapter is applied after the fact to make incompatible interfaces work. Strategy is similar in structure but varies an *algorithm*, not a whole implementation platform.

Real-World Use

GUI toolkits (widgets × platform back ends), JDBC (API × drivers), graphics engines (scene objects × OpenGL/Vulkan/Metal renderers), logging (loggers × sinks), Pimpl in C++ libraries.

Practice

Q1. (Easy) What does Bridge prevent?

Answer: Class explosion from combining two independent dimensions via inheritance.

Q2. (Medium) Bridge vs Adapter in one sentence?

Answer: Bridge is a planned separation so both sides can vary; Adapter retrofits an incompatible existing interface.

Q3. (Advanced) How would you add a TestRenderer that records calls for assertions?

Answer: Implement Renderer storing each text/bar call in a vector; draw widgets onto it in tests and assert on the recorded calls — no screen needed.

Interview Question

Q1. "Explain Bridge with an example where inheritance alone fails."

Answer: Shapes (circle, square) × rendering APIs (vector, raster). With inheritance you need VectorCircle, RasterCircle, VectorSquare, RasterSquare, and every new shape or API multiplies classes. Bridge gives Shape a reference to a DrawingApi; shapes call primitive drawing operations; new shapes and new APIs are added independently (N + M classes).

Quick Quiz

Q1. Which side holds the reference — abstraction or implementor?

Answer: The abstraction holds the implementor.

Q2. Which C++ idiom is a form of Bridge?

Answer: Pimpl.

Q3. How many classes for 3 widgets × 3 renderers?

Answer: 6 concrete classes (3 + 3) instead of 9.

Summary

Bridge separates abstraction and implementation hierarchies so both vary.

Related: Modules 33, 40, 63 (Pimpl).