|
| 1 | +--- |
| 2 | +title: GoF Design Patterns - Bridge |
| 3 | +date: 2026-02-11 |
| 4 | +category: Coding |
| 5 | +description: A practical guide to the Bridge pattern from the GoF collection:concepts, when to use it, a C++ implementation example, caveats, and references. |
| 6 | +tags: [Coding, DesignPattern] |
| 7 | +recommended: true |
| 8 | +thumbnail: assets/img/ogp.png |
| 9 | +--- |
| 10 | + |
| 11 | +Hello! This is Pan-kun. |
| 12 | + |
| 13 | +This article covers the Bridge pattern from the GoF (Gang of Four) design patterns. |
| 14 | +I explain it practically, including sample code (C++), how to build it, when to use it, and important caveats. |
| 15 | + |
| 16 | +## Introduction |
| 17 | + |
| 18 | +The Bridge pattern separates an abstraction from its implementation so the two can vary independently. |
| 19 | +When you use class inheritance, abstraction and implementation often become tightly coupled. |
| 20 | +The Bridge pattern provides a flexible alternative that lets you change either side without affecting the other. |
| 21 | + |
| 22 | +> The Bridge pattern is a software design pattern defined by the Gang of Four. It decouples an abstraction from its implementation so that the two can vary independently. |
| 23 | +> |
| 24 | +> Source: [Bridge pattern - Wikipedia](https://en.wikipedia.org/wiki/Bridge_pattern) |
| 25 | +
|
| 26 | +[!CARD](https://en.wikipedia.org/wiki/Bridge_pattern) |
| 27 | + |
| 28 | +## Use Cases |
| 29 | + |
| 30 | +Here are some typical situations where you might consider applying the Bridge pattern: |
| 31 | + |
| 32 | +**When you do not want to permanently bind abstraction and implementation.** |
| 33 | + Useful if you need to select or switch implementations at runtime. |
| 34 | +**When you need to extend both the abstraction hierarchy and the implementation hierarchy.** |
| 35 | + Keeps the number of classes under control by managing extension at independent layers. |
| 36 | +**When you want implementation changes to have minimal impact on clients.** |
| 37 | + Hides implementation details and can minimize recompilation scope (in C++ this is related to the Pimpl idiom). |
| 38 | +**When many classes share similar implementation with small variations.** |
| 39 | + Splitting the feature hierarchy from the implementation hierarchy reduces code duplication. |
| 40 | + |
| 41 | +## Structure |
| 42 | + |
| 43 | +To implement the Bridge pattern you typically create four roles of classes: |
| 44 | + |
| 45 | +1. **Abstraction** |
| 46 | + - The top-level class in the feature hierarchy. |
| 47 | + - Holds a reference to an `Implementor` and delegates work to it. |
| 48 | +2. **RefinedAbstraction** |
| 49 | + - Inherits from `Abstraction` and adds or refines behavior. |
| 50 | + - Defines concrete combinations of feature behavior. |
| 51 | +3. **Implementor** |
| 52 | + - The top-level interface (or abstract class) for the implementation hierarchy. |
| 53 | + - Defines the low-level API used by `Abstraction`. |
| 54 | +4. **ConcreteImplementor** |
| 55 | + - Implements the `Implementor` interface concretely. |
| 56 | + - Contains platform-specific or library-dependent code. |
| 57 | + |
| 58 | +Below is a UML representation. Notice the separation between the feature (abstraction) class hierarchy and the implementation class hierarchy — they are literally bridged. |
| 59 | + |
| 60 | +```mermaid |
| 61 | +classDiagram |
| 62 | + class Abstraction { |
| 63 | + -impl : Implementor |
| 64 | + +operation() |
| 65 | + } |
| 66 | +
|
| 67 | + class RefinedAbstraction { |
| 68 | + +operation() |
| 69 | + +extendedOperation() |
| 70 | + } |
| 71 | +
|
| 72 | + class Implementor { |
| 73 | + <<interface>> |
| 74 | + +operationImpl() |
| 75 | + } |
| 76 | +
|
| 77 | + class ConcreteImplementorA { |
| 78 | + +operationImpl() |
| 79 | + } |
| 80 | +
|
| 81 | + class ConcreteImplementorB { |
| 82 | + +operationImpl() |
| 83 | + } |
| 84 | +
|
| 85 | + Abstraction <|-- RefinedAbstraction |
| 86 | + Abstraction o-- Implementor |
| 87 | + Implementor <|.. ConcreteImplementorA |
| 88 | + Implementor <|.. ConcreteImplementorB |
| 89 | +``` |
| 90 | + |
| 91 | +## C++ Implementation Example |
| 92 | + |
| 93 | +Here we use a drawing example. |
| 94 | +We separate the abstract concept of a "shape" from a concrete "drawing API" implementation. |
| 95 | + |
| 96 | +This lets you add new shapes (e.g., rectangles, circles) on the shape side or add new drawing methods (e.g., OpenGL, DirectX) on the implementation side without changing the other. |
| 97 | + |
| 98 | +```cpp |
| 99 | +// Drawing API interface |
| 100 | +class DrawingAPI { |
| 101 | +public: |
| 102 | + virtual ~DrawingAPI() = default; |
| 103 | + virtual void drawCircle(double x, double y, double radius) = 0; |
| 104 | +}; |
| 105 | + |
| 106 | +// Concrete Drawing API 1 |
| 107 | +class DrawingAPI1 : public DrawingAPI { |
| 108 | +public: |
| 109 | + void drawCircle(double x, double y, double radius) override { |
| 110 | + std::cout << "API1.circle at " << x << ":" << y << " radius " << radius << std::endl; |
| 111 | + } |
| 112 | +}; |
| 113 | + |
| 114 | +// Concrete Drawing API 2 |
| 115 | +class DrawingAPI2 : public DrawingAPI { |
| 116 | +public: |
| 117 | + void drawCircle(double x, double y, double radius) override { |
| 118 | + std::cout << "API2.circle at " << x << ":" << y << " radius " << radius << std::endl; |
| 119 | + } |
| 120 | +}; |
| 121 | + |
| 122 | +// Abstract shape class |
| 123 | +class Shape { |
| 124 | +protected: |
| 125 | + DrawingAPI* drawingAPI; // holds reference to Implementor |
| 126 | + |
| 127 | +public: |
| 128 | + Shape(DrawingAPI* api) : drawingAPI(api) {} |
| 129 | + virtual ~Shape() = default; |
| 130 | + |
| 131 | + virtual void draw() = 0; // high-level operation |
| 132 | + virtual void resizeByPercentage(double pct) = 0; // high-level operation |
| 133 | +}; |
| 134 | + |
| 135 | +// Concrete shape: circle |
| 136 | +class CircleShape : public Shape { |
| 137 | +private: |
| 138 | + double x, y, radius; |
| 139 | + |
| 140 | +public: |
| 141 | + CircleShape(double x, double y, double radius, DrawingAPI* api) |
| 142 | + : Shape(api), x(x), y(y), radius(radius) {} |
| 143 | + |
| 144 | + void draw() override { |
| 145 | + // Delegate actual drawing to the drawingAPI implementation |
| 146 | + drawingAPI->drawCircle(x, y, radius); |
| 147 | + } |
| 148 | + |
| 149 | + void resizeByPercentage(double pct) override { |
| 150 | + radius *= (1.0 + pct / 100.0); |
| 151 | + } |
| 152 | +}; |
| 153 | + |
| 154 | +int main() { |
| 155 | + DrawingAPI1 api1; |
| 156 | + DrawingAPI2 api2; |
| 157 | + |
| 158 | + // Same conceptual "circle" but instantiated with different drawing APIs |
| 159 | + CircleShape circle1(1, 2, 3, &api1); |
| 160 | + CircleShape circle2(5, 7, 11, &api2); |
| 161 | + |
| 162 | + circle1.resizeByPercentage(10); |
| 163 | + circle2.resizeByPercentage(10); |
| 164 | + |
| 165 | + std::cout << "--- Circle 1 with API 1 ---" << std::endl; |
| 166 | + circle1.draw(); |
| 167 | + |
| 168 | + std::cout << "--- Circle 2 with API 2 ---" << std::endl; |
| 169 | + circle2.draw(); |
| 170 | + |
| 171 | + return 0; |
| 172 | +} |
| 173 | + |
| 174 | +// Output |
| 175 | +// --- Circle 1 with API 1 --- |
| 176 | +// API1.circle at 1:2 radius 3.3 |
| 177 | +// --- Circle 2 with API 2 --- |
| 178 | +// API2.circle at 5:7 radius 12.1 |
| 179 | +``` |
| 180 | +
|
| 181 | +In this example, `Shape` holds a pointer to a `DrawingAPI`. |
| 182 | +When `CircleShape::draw` is invoked, the actual drawing logic is delegated to the `DrawingAPI` implementation. |
| 183 | +
|
| 184 | +## Advantages / Disadvantages |
| 185 | +
|
| 186 | +### Advantages |
| 187 | +
|
| 188 | +- **Implementation hiding and reduced dependencies** |
| 189 | + Client code needs to know only the abstraction (`Abstraction`) and not the implementation details. This can shorten compile times and enable looser coupling. |
| 190 | +- **Improved extensibility** |
| 191 | + You can extend the feature hierarchy and the implementation hierarchy independently. Adding a new feature doesn't affect implementation classes, and adding a new implementation doesn't affect feature classes. |
| 192 | +- **Runtime implementation switching** |
| 193 | + By replacing the `Implementor` object at runtime you can change behavior dynamically. |
| 194 | +
|
| 195 | +### Disadvantages |
| 196 | +
|
| 197 | +- **Increased design complexity** |
| 198 | + For small systems where simple inheritance is sufficient, Bridge can increase the number of classes and unnecessarily complicate the structure. |
| 199 | +- **Potentially reduced readability** |
| 200 | + Because behavior is delegated from the abstraction to implementation classes, understanding the full behavior may require navigating multiple classes, which can make the code less intuitive. |
| 201 | +
|
| 202 | +## Conclusion |
| 203 | +
|
| 204 | +The Bridge pattern is a powerful technique to keep systems flexible by separating feature extension from implementation extension. |
| 205 | +It truly shines in large projects where future change requirements are unclear. |
| 206 | +However, it can be overengineering for small projects; a good rule is to introduce it when you actually see class explosion or when refactoring for clearer separation. |
| 207 | +
|
| 208 | +References: |
| 209 | +
|
| 210 | +[!CARD](https://en.wikipedia.org/wiki/Bridge_pattern) |
| 211 | +
|
| 212 | +That's all for now — next time I'll cover another GoF pattern. |
| 213 | +The sample implementations shown here are simplified for learning purposes. Evaluate carefully before adopting them in production. |
| 214 | +
|
| 215 | +Thanks for reading — Pan-kun! |
0 commit comments