【设计模式精讲】11.桥接模式(Bridge)
【摘要】:形状两种、渲染器两种,继承矩阵先写四个子类;三角形一加入变六个,OpenGL 渲染器一加入变十二个------两个维度各自增长,子类数按乘法结算。本文从图形库的类爆炸讲起,给出桥接模式的 GoF 意图:把「抽象」与「实现」拆成两个独立的层次结构,用组合关系在中间架桥,类数从 n×m 塌缩为 n+m,两边各自加类互不相扰。C++ 实现覆盖引用造桥的传统版与运行期可换桥的现代版,并厘清 PIMPL 与桥接的血缘:前者是单实现的桥接退化,后者是多实现的 PIMPL 前瞻。文末对照 POCO 的 SocketImpl 家族、AOSP 的 HAL 工业级桥接与 C++17 的 pmr 分配器桥。读完你能在设计期就识破「继承矩阵」这个结构性陷阱。
【关键词】:桥接、抽象与实现分离、继承矩阵、组合替代继承、PIMPL、多维度变化
【代码基准】:C++17
1. 两个维度一相乘,子类排成矩阵
图形库要同时支持两类增长:形状 会从圆、方扩到三角形、星形;渲染方式会从光栅、矢量扩到 OpenGL、打印。用继承同时表达两个维度,标准写法是「形状 × 渲染器」各写一个子类:
cpp
// 说明性片段(省略各子类实现)
// ❌ 两维度相乘的继承矩阵
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() = 0;
};
class RasterCircle : public Shape {};
class VectorCircle : public Shape {};
class RasterSquare : public Shape {};
class VectorSquare : public Shape {};
两形状两渲染器,4 个子类,尚可忍受。加三角形,6 个;再加 OpenGL 渲染器,12 个------任一维度的每次增长,都要与另一维度的全部存量相乘 ,而且乘出来的是手写代码:RasterTriangle 与 RasterCircle 之间除了「圆形/三角形」的差异,光栅逻辑全部重抄(或上移到中间基类,又制造出菱形继承的诱惑)。
更深的病根在语义:RasterCircle 这个名字回答的是「圆的光栅画法」,但 「圆」与「光栅」根本不是同一种分类轴 ------前者是几何语义,后者是绘制机制。继承只能表达一条「is-a」链,把两条独立的轴塞进同一棵继承树,等于强迫语言用它不擅长的方式编码组合关系。第 2 篇的组合复用原则早已给出路标:「跨轴复用」应该用组合,不该用继承。桥接模式就是这条原则在「两个维度都要继续增长」场景下的完整落地。
2. 模式意图与定义
- 一句话定义 :将抽象部分与它的实现部分分离,使它们都可以独立地变化。
解决的问题:多个独立变化维度被继承焊死在同一个类层次里,导致子类按维度乘积膨胀、任一维度扩展全量重写。 - GoF 原文意图 :Decouple abstraction from its implementation so that the two can vary independently. 注意 GoF 的「抽象」「实现」是 术语而非字面:抽象指高层的策略与语义(形状),实现指底层的机制与平台(渲染器)------不是说「抽象类对应实现类」。
- Refactoring Guru 的表述 :桥接是一种结构型模式,可将一个大类或一系列紧密相关的类拆分为 两个独立的层次结构(抽象与实现),并能在使用时对它们独立开发------抽象层内部持有实现层的对象,「桥」就是这条组合关系。
三条定性先立住:
- 桥是组合,不是继承。抽象类持有一个实现层接口的引用/指针,所有跨维度的能力都过桥调用------继承矩阵从此变成长方形的两张独立类表。
- 类数从 n×m 变 n+m。形状 n 个、渲染器 m 个,各自加类,互不惊动;这正是「独立变化」的可度量形式。
- 两边都可以再派生 。抽象侧的
Circle可派生DashedCircle,实现侧的RasterRenderer可派生AntialiasedRaster------两张表各自完整,这是它与「一次性注入一个策略」的区别(详见第 6 节与策略的辨析)。
GoF 自己的场景是 Window:抽象层的 Window 及其派生(IconWindow、TransientWindow),桥到实现层的 WindowImp(XWindowImp、PMWindowImp)------窗口语义的演化与窗口系统的演化彻底解耦。
3. UML 图 + 结构说明
#mermaid-svg-pjaJUchP71yOFmUJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pjaJUchP71yOFmUJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pjaJUchP71yOFmUJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pjaJUchP71yOFmUJ .error-icon{fill:#552222;}#mermaid-svg-pjaJUchP71yOFmUJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pjaJUchP71yOFmUJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pjaJUchP71yOFmUJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pjaJUchP71yOFmUJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pjaJUchP71yOFmUJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pjaJUchP71yOFmUJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pjaJUchP71yOFmUJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pjaJUchP71yOFmUJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pjaJUchP71yOFmUJ .marker.cross{stroke:#333333;}#mermaid-svg-pjaJUchP71yOFmUJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pjaJUchP71yOFmUJ p{margin:0;}#mermaid-svg-pjaJUchP71yOFmUJ g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-pjaJUchP71yOFmUJ g.classGroup text .title{font-weight:bolder;}#mermaid-svg-pjaJUchP71yOFmUJ .cluster-label text{fill:#333;}#mermaid-svg-pjaJUchP71yOFmUJ .cluster-label span{color:#333;}#mermaid-svg-pjaJUchP71yOFmUJ .cluster-label span p{background-color:transparent;}#mermaid-svg-pjaJUchP71yOFmUJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pjaJUchP71yOFmUJ .cluster text{fill:#333;}#mermaid-svg-pjaJUchP71yOFmUJ .cluster span{color:#333;}#mermaid-svg-pjaJUchP71yOFmUJ .nodeLabel,#mermaid-svg-pjaJUchP71yOFmUJ .edgeLabel{color:#131300;}#mermaid-svg-pjaJUchP71yOFmUJ .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-pjaJUchP71yOFmUJ .label text{fill:#131300;}#mermaid-svg-pjaJUchP71yOFmUJ .labelBkg{background:#ECECFF;}#mermaid-svg-pjaJUchP71yOFmUJ .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-pjaJUchP71yOFmUJ .classTitle{font-weight:bolder;}#mermaid-svg-pjaJUchP71yOFmUJ .node rect,#mermaid-svg-pjaJUchP71yOFmUJ .node circle,#mermaid-svg-pjaJUchP71yOFmUJ .node ellipse,#mermaid-svg-pjaJUchP71yOFmUJ .node polygon,#mermaid-svg-pjaJUchP71yOFmUJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pjaJUchP71yOFmUJ .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ g.clickable{cursor:pointer;}#mermaid-svg-pjaJUchP71yOFmUJ g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-pjaJUchP71yOFmUJ g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-pjaJUchP71yOFmUJ .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-pjaJUchP71yOFmUJ .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-pjaJUchP71yOFmUJ .dashed-line{stroke-dasharray:3;}#mermaid-svg-pjaJUchP71yOFmUJ .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-pjaJUchP71yOFmUJ #compositionStart,#mermaid-svg-pjaJUchP71yOFmUJ .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ #compositionEnd,#mermaid-svg-pjaJUchP71yOFmUJ .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ #dependencyStart,#mermaid-svg-pjaJUchP71yOFmUJ .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ #dependencyStart,#mermaid-svg-pjaJUchP71yOFmUJ .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ #extensionStart,#mermaid-svg-pjaJUchP71yOFmUJ .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ #extensionEnd,#mermaid-svg-pjaJUchP71yOFmUJ .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ #aggregationStart,#mermaid-svg-pjaJUchP71yOFmUJ .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ #aggregationEnd,#mermaid-svg-pjaJUchP71yOFmUJ .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ #lollipopStart,#mermaid-svg-pjaJUchP71yOFmUJ .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ #lollipopEnd,#mermaid-svg-pjaJUchP71yOFmUJ .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-pjaJUchP71yOFmUJ .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-pjaJUchP71yOFmUJ .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pjaJUchP71yOFmUJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pjaJUchP71yOFmUJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pjaJUchP71yOFmUJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 抽象层
实现层
桥(组合)
<<abstract>>
Shape
#impl_ Renderer
+draw() : void
Circle
-x_ int
-y_ int
-r_ int
+draw() : void
Square
+draw() : void
<<interface>>
Renderer
+circle(x, y, r) : void
+square(x, y, w) : void
RasterRenderer
+circle(x, y, r) : void
+square(x, y, w) : void
VectorRenderer
+circle(x, y, r) : void
+square(x, y, w) : void
四个参与者(GoF 命名):
- 抽象(Abstraction) :
Shape,维护一个指向实现层接口的引用,定义高层语义操作; - 扩展抽象(Refined Abstraction) :
Circle/Square,在抽象语义上细化,实现方式是「过桥调原语」; - 实现方(Implementor)接口 :
Renderer,声明抽象层所需的一族 底层原语 (circle/square)------注意它不为每个高层操作设方法,只提供可组合的原语; - 具体实现方(Concrete Implementor) :
RasterRenderer/VectorRenderer,各自实现全部原语。
图上唯一要盯的边是 Shape o-- Renderer:整张图的其他边都是各自层次的纵向继承,只有这条横向组合边是「桥」。对照第 1 节的继承矩阵:原来每对(形状,渲染器)组合是一个类,现在「形状」与「渲染器」各自成类、组合推迟到 运行时的装配 完成------把「组合爆炸」从代码里赶进了装配点,这步转移就是模式的全部分量。
4. 传统 C++ 写法(C++11 之前)
C++98 完整形态:抽象类持实现层的引用成员(装配期定终身),实现层接口全纯虚:
cpp
// C++98/03 写法
#include <stdio.h>
// ---- 实现方接口:一族底层原语 ----
class Renderer {
public:
virtual ~Renderer() {}
virtual void circle(int x, int y, int r) = 0;
virtual void square(int x, int y, int w) = 0;
protected:
Renderer() {}
private:
Renderer(const Renderer&);
Renderer& operator=(const Renderer&);
};
class RasterRenderer : public Renderer {
public:
void circle(int x, int y, int r) {
printf("[光栅] 逐像素画圆 (r=%d)\n", r);
}
void square(int x, int y, int w) {
printf("[光栅] 逐像素画方 (w=%d)\n", w);
}
};
class VectorRenderer : public Renderer {
public:
void circle(int, int, int) {
printf("[矢量] 输出 <circle> 图元\n");
}
void square(int, int, int) {
printf("[矢量] 输出 <rect> 图元\n");
}
};
// ---- 抽象方:持桥、定义高层语义 ----
class Shape {
public:
Shape(Renderer& impl) : impl_(impl) {} // 架桥
virtual ~Shape() {}
virtual void draw() = 0;
protected:
Renderer& impl_; // 桥本体:实现层入口
};
class Circle : public Shape {
public:
Circle(Renderer& impl, int x, int y, int r)
: Shape(impl), x_(x), y_(y), r_(r) {}
void draw() {
impl_.circle(x_, y_, r_); // 过桥
}
private:
int x_, y_, r_;
};
class Square : public Shape {
public:
Square(Renderer& impl, int x, int y, int w)
: Shape(impl), x_(x), y_(y), w_(w) {}
void draw() {
impl_.square(x_, y_, w_);
}
private:
int x_, y_, w_;
};
int main() {
RasterRenderer raster;
VectorRenderer vector;
Circle(raster, 3, 4, 5).draw();
Circle(vector, 3, 4, 5).draw(); // 同一形状,
Square(raster, 1, 1, 2).draw(); // 不同桥
return 0;
}
四个类(2 形状 + 2 渲染器)替代第 1 节的 4 个矩阵类,且此后:加 Triangle 只在抽象侧写一个类,两个渲染器立即对它生效;加 GlRenderer 只在实现侧写一个类,全部形状立即换得动------「独立变化」四个字,此刻是可以数的。
传统写法的铁律:桥在构造时架好就不再换 (引用成员定终身------运行期换桥的需求见第 5 节);实现方接口只放原语、不放高层操作 ------draw() 属于抽象语义,circle() 属于绘制原语,接口一旦按高层操作设计,实现侧就要跟着抽象侧的每次演化重排;抽象方不 new 具体实现------桥的装配(哪个形状配哪台渲染器)由更外层(工厂,第 6 篇)决定,抽象方只管跨桥。
5. 现代 C++ 进阶写法
升级零:shared_ptr 造桥,装配与共享都在运行期 。实现层对象往往无状态或可共享(一台渲染器伺候千万个形状),shared_ptr 最贴切;桥也顺势支持换装:
cpp
// 节选(Renderer 家族同第 4 节,略)
#include <memory>
class Shape {
public:
explicit Shape(
std::shared_ptr<Renderer> impl)
: impl_(std::move(impl)) {}
virtual ~Shape() = default;
virtual void draw() = 0;
void setRenderer( // 运行期换桥
std::shared_ptr<Renderer> impl) {
impl_ = std::move(impl);
}
protected:
std::shared_ptr<Renderer> impl_;
};
// 装配:形状与渲染器此刻才相遇
auto circle = Circle(
std::make_shared<RasterRenderer>(),
3, 4, 5);
circle.draw();
circle.setRenderer(
std::make_shared<VectorRenderer>());
circle.draw(); // 同一对象过另一座桥
桥的三种持法各有其位:引用 (第 4 节)= 装配期定终身、零开销,最简单也最常够用;unique_ptr = 一对一独占(这条形状独享一台有状态实现);shared_ptr = 多形状共享实现、可换桥。默认从引用开始,需求来了再升级------「为可能的换桥先付 shared_ptr 的账」是过度设计。
改进一:PIMPL------桥接的单实现退化 。第 14 篇把 PIMPL 当编译防火墙用,放到本篇的血缘里看更准确:PIMPL 是 实现层只有一个具体实现 的桥接------抽象类持 unique_ptr<Impl>,桥架好了永不换;桥接则是 实现层会继续生长 的 PIMPL。工程判断由此清晰:第二台实现的预期存在,才值得把 Impl 提升为带虚函数的 Implementor 接口层次;否则一个具体 Impl 类加 unique_ptr 就是全部(连虚函数都不必有)。反过来,用了 PIMPL 的类日后要支持多后端,升级路径就是沿桥接把 Impl 抽成接口------两个模式根本是同一件事的两个成熟度。
改进二:模板参数------编译期的桥。组合维度若在编译期就能定死,模板参数可以替虚函数造桥,间接层消失:
cpp
// 节选(编译期桥:Impl 是模板参数)
template <typename Impl>
class CircleT {
public:
CircleT(Impl impl, int x, int y, int r)
: impl_(std::move(impl)),
x_(x), y_(y), r_(r) {}
void draw() const {
impl_.circle(x_, y_, r_); // 静态分派
}
private:
Impl impl_;
int x_, y_, r_;
};
// CircleT<RasterRenderer> 与
// CircleT<VectorRenderer> 是两个静态组合
注意代价的变化而非消失:n×m 的组合以 模板实例 的形式回来了------只是从「手写源码」变成「编译器生成产物」,换来内联与零虚表开销。判断标准与第 13 篇 CRTP 一致:组合真实全用且在热路径,才值得编译期造桥;否则运行期桥的灵活性完胜。
展望 :C++20 concepts 可为 Impl 写出显式契约(「必须有 circle/square 原语」),模板桥的错误信息从模板天书变成接口检查失败;std::pmr 已把「分配策略」这个最古老的实现维度做成了标准库级的桥(第 7 节)。
6. 优缺点与适用场景
- ✅ 优点(GoF 后果清单):接口与实现分离 ------两层独立扩展、独立修改、独立发布(配合第 14 篇 PIMPL,连编译都可分离);类数从乘法变加法 ,n+m 个类覆盖全部 n×m 组合;实现细节对客户端可透明 ------换渲染器、换平台后端,抽象侧代码零改动;运行期换桥 (
shared_ptr版)让「同语义、不同机制」可以按配置热切。 - ❌ 缺点:预先设计成本 ------要在只有一个实现时就想清楚抽象/实现的切线,切早了、切错了都是过度设计(单实现场景 PIMPL 足矣);Implementor 接口是新的耦合点------原语粒度定得不合适(太肥则每个新实现要背全族方法,太瘦则抽象侧要拼装过多次过桥调用);一层间接与一次虚分派的固定开销。
- 🎯 适用场景:跨平台/多后端 (GUI 的 Window/WindowImp、音频的 ALSA/CoreAudio、存储的内存/磁盘/云);语义 × 机制双维度都要长的(图形/序列化/日志目标);要在运行期切换实现(测试桩与真实现互换,第 26 篇策略会再遇到);任何「PIMPL 用着用着实现层开始分叉」的时刻。
〔辨析〕桥接 vs 适配器(第 10 篇):适配器是 事后缝合 已有的接口不匹配,双方类已存在;桥接是 设计期预先分层 ,让匹配关系本身可变------一个救火,一个布防。桥接 vs 策略(第 26 篇):策略通常换「一个算法」(上下文持一个算法槽,算法间互不构成层次);桥接是「一张层次表 × 一张层次表」的 结构性分离,抽象侧与实现侧各自还能派生演化------可以说策略是桥接的最小切片。桥接 vs 抽象工厂(第 4 篇):工厂负责「成族地造」出正确的组合,桥接负责「造出来之后怎么协作」------桥上的具体实现常常正是工厂递过来的。
7. 开源项目中的身影
POCO:每个 Socket 都是桥的外壳 。POCO 的网络层把 StreamSocket(抽象)与 StreamSocketImpl(实现)拆成两层:StreamSocket 的每个 API 都是薄薄一转发,桥那头的 Impl 家族里有 IPv4/IPv6 的普通实现,也有叠了 OpenSSL 的 SecureStreamSocketImpl:
cpp
// 说明性片段(需链接 PocoNet,签名有简化)
#include <Poco/Net/SocketAddress.h>
#include <Poco/Net/StreamSocket.h>
Poco::Net::StreamSocket sock(
Poco::Net::SocketAddress(
"example.com", 80));
// sock 的每个调用都过桥到
// StreamSocketImpl 家族的某个实现
点评:SecureStreamSocket 并不是 StreamSocket 的平行子类,而是「同一抽象 + 另一座桥(安全实现)」------这正是第 1 节继承矩阵的解法在库设计里的样子。POCO 全库的 Impl 后缀类(LogFileImpl、ProcessImpl)都是同一条桥的变体,抽象侧稳定、实现侧随平台生长。
AOSP:HAL 是工业级的大桥 。Android 的硬件抽象层把 framework(抽象层)与厂商驱动(实现层)拆成两张独立演化的表:以音频为例,audio_hw_device 这个 C 结构定义了一族函数指针(实现方接口),framework 的 AudioFlinger 只调这族指针,背后是高通还是联发科的实现、以 .so 形式 dlopen 进来,抽象侧一无所知:
cpp
// 说明性片段(节选自 AOSP audio HAL,签名有简化)
struct audio_hw_device {
int (*open_output_stream)(
struct audio_hw_device* dev,
struct audio_stream_out** out);
// ......一族函数指针 = 实现方接口
};
// 框架侧只过桥,厂商侧各自整表实现
点评:Android 每年更新框架、各厂商随时更新驱动,两张表 独立发版 的需求就是「独立变化」的工业表达。函数指针表这种 C 风格的 Implementor 也提醒我们:桥接不依赖虚函数语法,只依赖「抽象持接口、接口有一族实现」这个结构。
标准库:std::pmr 把分配器铸成了桥 。容器是抽象层(vector 的语义),内存来源是实现层(栈上缓冲、内存池、同步池)------若为每种组合写一个容器类,就是分配器版的继承矩阵。C++17 的 std::pmr 用多态内存资源架桥:
cpp
// C++17:容器(抽象)× 内存来源(实现)分离
#include <memory_resource>
#include <vector>
char buf[1024];
std::pmr::monotonic_buffer_resource
pool(buf, sizeof buf);
std::pmr::vector<int> xs{&pool}; // 过桥分配
// 换实现不换容器,换容器不换实现
点评:分配器其实是最早住进标准库的桥------STL 从第一天就把「分配策略」抽成了独立维度,std::pmr 只是把它从模板参数升级为运行期可换的多态桥(恰好复刻了第 5 节「编译期桥 → 运行期桥」的整条谱系)。能在标准库待上三十年的结构,都是模式结构里最耐磨的那几件。
本篇小结
继承只能编码一条变化轴;两条轴硬塞进一棵树,就得到按乘法结算的矩阵。桥接的解法是把其中一条轴(实现/机制/平台)整体拎出来立户:抽象类持实现方接口的引用,这条组合关系就是桥;n×m 的子类塌缩成 n+m,两张表各自派生、独立演化,组合推迟到装配期。C++ 的谱系值得一并记住:引用造桥最简,shared_ptr 造桥可换,模板参数造编译期桥换性能;PIMPL 是单实现的桥接退化,实现层一分叉就升级成桥接。判断先于实现:第二个维度 真的会来 才架桥,永远只有一个实现就安心 PIMPL。下一篇组合模式,从「两条独立变化的轴」转向「一群对象怎么长成一棵树」------部分与整体的统一。
本文模式定义与角色划分参考了 Refactoring Guru《设计模式》中文版「桥接」一章,意图译文、Window/WindowImp 示例与后果清单参考了 GoF《Design Patterns》第 4 章 Bridge 一节。