新谈设计模式 Chapter 07 — 桥接模式 Bridge

张开发
2026/4/13 11:30:01 15 分钟阅读

分享文章

新谈设计模式 Chapter 07 — 桥接模式 Bridge
Chapter 07 — 桥接模式 Bridge灵魂速记遥控器和电视机分开卖——换遥控器不用换电视换电视不用换遥控器。秒懂类比你家有一个万能遥控器可以控制索尼电视、三星电视、LG 电视。你换了一台新电视遥控器还能用你换了一个新遥控器电视还能用。两个维度独立变化互不影响。这就是桥接。如果不用桥接你会遇到类爆炸索尼基础遥控器、索尼高级遥控器、 三星基础遥控器、三星高级遥控器、 LG基础遥控器、LG高级遥控器……2 种遥控器 × 3 种电视 6 个类。如果维度再多指数级爆炸。问题引入// 灾难现场每种形状 × 每种颜色 类爆炸classRedCircle:publicShape{...};classBlueCircle:publicShape{...};classGreenCircle:publicShape{...};classRedSquare:publicShape{...};classBlueSquare:publicShape{...};classGreenSquare:publicShape{...};// 3 个颜色 × 2 个形状 6 个类// 再加一种颜色再加 2 个类根因把两个独立的维度形状、颜色揉进了同一个继承体系。模式结构┌───────────┐ ┌────────────┐ │Abstraction│──────────→│Implementor │ ← 桥 │ (遥控器) │ has-a │ (电视机) │ ├───────────┤ ├────────────┤ │ operate()│ │ implOp() │ └─────┬─────┘ └─────┬──────┘ │ 继承 │ 继承 ┌─────┴─────┐ ┌──────┴──────┐ │RefinedAbst│ │ConcreteImpl │ │(高级遥控器)│ │(索尼/三星) │ └───────────┘ └─────────────┘ 两个维度各自独立扩展互不干扰。C 实现#includeiostream#includememory#includestring// 实现维度渲染引擎 classRenderer{public:virtual~Renderer()default;virtualvoidrenderCircle(floatx,floaty,floatradius)const0;virtualvoidrenderRect(floatx,floaty,floatw,floath)const0;};classOpenGLRenderer:publicRenderer{public:voidrenderCircle(floatx,floaty,floatr)constoverride{std::coutOpenGL 画圆: (x,y) rr\n;}voidrenderRect(floatx,floaty,floatw,floath)constoverride{std::coutOpenGL 画矩形: (x,y) w×h\n;}};classVulkanRenderer:publicRenderer{public:voidrenderCircle(floatx,floaty,floatr)constoverride{std::coutVulkan 画圆: (x,y) rr\n;}voidrenderRect(floatx,floaty,floatw,floath)constoverride{std::coutVulkan 画矩形: (x,y) w×h\n;}};// 抽象维度形状 classShape{public:explicitShape(std::shared_ptrRendererrenderer):renderer_(std::move(renderer)){}virtual~Shape()default;virtualvoiddraw()const0;protected:std::shared_ptrRendererrenderer_;// ← 这就是桥};classCircle:publicShape{public:Circle(std::shared_ptrRendererrenderer,floatx,floaty,floatr):Shape(std::move(renderer)),x_(x),y_(y),r_(r){}voiddraw()constoverride{renderer_-renderCircle(x_,y_,r_);// 委托给实现层}private:floatx_,y_,r_;};classRectangle:publicShape{public:Rectangle(std::shared_ptrRendererrenderer,floatx,floaty,floatw,floath):Shape(std::move(renderer)),x_(x),y_(y),w_(w),h_(h){}voiddraw()constoverride{renderer_-renderRect(x_,y_,w_,h_);}private:floatx_,y_,w_,h_;};intmain(){autoopenglstd::make_sharedOpenGLRenderer();autovulkanstd::make_sharedVulkanRenderer();// 同一个形状不同渲染引擎Circlec1(opengl,10,20,5);Circlec2(vulkan,10,20,5);std::cout 用 OpenGL 画圆 \n;c1.draw();std::cout 用 Vulkan 画圆 \n;c2.draw();std::cout\n 用 Vulkan 画矩形 \n;Rectanglerect(vulkan,0,0,100,50);rect.draw();// 要加 DirectX只需写一个 DirectXRenderer不碰 Shape 代码// 要加 Triangle只需写一个 Triangle 类不碰 Renderer 代码}输出 用 OpenGL 画圆 OpenGL 画圆: (10,20) r5 用 Vulkan 画圆 Vulkan 画圆: (10,20) r5 用 Vulkan 画矩形 Vulkan 画矩形: (0,0) 100×50核心洞察桥接的精髓把一个类中的两个变化维度拆成两个独立的继承体系用组合连起来。形状变在 Shape 这边加子类渲染引擎变在 Renderer 那边加子类两边互不影响没有桥接M 种形状 × N 种渲染器 M×N 个类有了桥接M 种形状 N 种渲染器 MN 个类什么时候用✅ 适合❌ 别用一个类有两个或以上独立变化的维度只有一个变化维度想避免类爆炸类数量本来就不多需要运行时切换实现实现不会变跨平台/跨设备场景不需要跨平台防混淆Bridge vs AdapterBridgeAdapter时机设计时就分离两个维度事后补救不兼容的接口目的拆分维度避免类爆炸让现有类配合使用接口自己定义抽象和实现适配已有的接口一句话分清Bridge 是提前设计的分离Adapter 是事后补救的兼容。Bridge vs StrategyBridgeStrategy目的分离结构的两个维度替换算法/行为结构两个维度都有继承体系一个上下文 多个策略关注点架构层面的解耦行为层面的灵活替换Bridge 也用组合Strategy 也用组合。但Bridge 的重点是拆分两个维度Strategy 的重点是换算法。现代 C 小贴士如果渲染引擎在编译期确定可以用模板避免虚函数开销templatetypenameRendererTclassCircle{public:Circle(RendererT renderer,floatx,floaty,floatr):renderer_(std::move(renderer)),x_(x),y_(y),r_(r){}voiddraw()const{renderer_.renderCircle(x_,y_,r_);}private:RendererT renderer_;floatx_,y_,r_;};这是静态桥接编译期绑定运行时零开销。

更多文章