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 pressureStep-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 <|.. HudRendererCommon 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).