【设计模式精讲】11.桥接模式(Bridge)

【设计模式精讲】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 个------任一维度的每次增长,都要与另一维度的全部存量相乘 ,而且乘出来的是手写代码:RasterTriangleRasterCircle 之间除了「圆形/三角形」的差异,光栅逻辑全部重抄(或上移到中间基类,又制造出菱形继承的诱惑)。

更深的病根在语义:RasterCircle 这个名字回答的是「圆的光栅画法」,但 「圆」与「光栅」根本不是同一种分类轴 ------前者是几何语义,后者是绘制机制。继承只能表达一条「is-a」链,把两条独立的轴塞进同一棵继承树,等于强迫语言用它不擅长的方式编码组合关系。第 2 篇的组合复用原则早已给出路标:「跨轴复用」应该用组合,不该用继承。桥接模式就是这条原则在「两个维度都要继续增长」场景下的完整落地。

2. 模式意图与定义

  • 一句话定义 :将抽象部分与它的实现部分分离,使它们都可以独立地变化。
    解决的问题:多个独立变化维度被继承焊死在同一个类层次里,导致子类按维度乘积膨胀、任一维度扩展全量重写。
  • GoF 原文意图Decouple abstraction from its implementation so that the two can vary independently. 注意 GoF 的「抽象」「实现」是 术语而非字面:抽象指高层的策略与语义(形状),实现指底层的机制与平台(渲染器)------不是说「抽象类对应实现类」。
  • Refactoring Guru 的表述 :桥接是一种结构型模式,可将一个大类或一系列紧密相关的类拆分为 两个独立的层次结构(抽象与实现),并能在使用时对它们独立开发------抽象层内部持有实现层的对象,「桥」就是这条组合关系。

三条定性先立住:

  1. 桥是组合,不是继承。抽象类持有一个实现层接口的引用/指针,所有跨维度的能力都过桥调用------继承矩阵从此变成长方形的两张独立类表。
  2. 类数从 n×m 变 n+m。形状 n 个、渲染器 m 个,各自加类,互不惊动;这正是「独立变化」的可度量形式。
  3. 两边都可以再派生 。抽象侧的 Circle 可派生 DashedCircle,实现侧的 RasterRenderer 可派生 AntialiasedRaster------两张表各自完整,这是它与「一次性注入一个策略」的区别(详见第 6 节与策略的辨析)。

GoF 自己的场景是 Window:抽象层的 Window 及其派生(IconWindowTransientWindow),桥到实现层的 WindowImpXWindowImpPMWindowImp)------窗口语义的演化与窗口系统的演化彻底解耦。

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 后缀类(LogFileImplProcessImpl)都是同一条桥的变体,抽象侧稳定、实现侧随平台生长。

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 一节。

相关推荐
小小、码农19 分钟前
【网络】套接字(Socket)编程——TCP版
linux·服务器·网络·c++·网络协议·tcp/ip
天空之城--29 分钟前
C++ ODR-use 深度解析:从原理到实战
c++
旖旎夜光1 小时前
LeetCode 974:和可被 K 整除的子数组(前缀和) —— 题解
数据结构·c++·算法·leetcode·前缀和
小小龙学IT1 小时前
libuv 开源异步 I/O 事件循环库深度解析:Node.js 的心脏,C++ 高性能网络程序的引擎
c++·开源·node.js
彷徨而立1 小时前
【C/C++】多线程读写普通 int 变量的一些问题(二)
c语言·c++
一只旭宝1 小时前
五种线程池设计
c++·笔记
某林2122 小时前
机器人收不住、转不动?执行器死区的原理与三层补偿设计
前端·网络·c++·架构·机器人
程与留16 小时前
15_国际化和本地化:tr()、ts 文件、QM 文件、多语言切换
c++·qt
程与留17 小时前
14_Qt 样式表(QSS)入门(语法、选择器、美化实战)
c++·qt