ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

设计模式7——桥接模式(Bridge Pattern)

设计模式7——桥接模式(Bridge Pattern)

1. 提出桥接模式前的痛点场景

设想一个图形绘制系统:系统中有多种形状(Shape),例如圆形(Circle)、方形(Square);同时有多种渲染方式(Renderer),例如 OpenGL、Vulkan、DirectX。

如果直接用继承来建模,最容易想到的方案是为「形状 × 渲染方式」的每一种组合都创建一个类:

Shape(形状) ├── Circle(圆形) │ ├── CircleOpenGL │ ├── CircleVulkan │ └── CircleDirectX └── Square(方形) ├── SquareOpenGL ├── SquareVulkan └── SquareDirectX

当系统规模较小时,这种方式似乎可以接受。然而随着系统演进,问题会集中爆发:

  • 类的数量呈乘积式爆炸:形状种类 M 乘以渲染方式 N,就需要 M × N 个具体类。形状增加 1 种、渲染方式增加 1 种,都需要成倍新增子类。
  • 新增一个维度极不灵活:如果要再加入线条风格(实线、虚线),类的组合数量会进一步膨胀,几乎无法维护。
  • 代码高度重复:每个子类里都重复写形状计算逻辑和渲染调用逻辑,违背单一职责原则。

2. 模式基本思路

桥接模式(Bridge Pattern)的核心思想是:将抽象部分与实现部分分离,使它们都可以独立地变化。

具体做法是把继承关系改造为组合关系:

  • 抽象部分(Abstraction):面向业务的高层概念,比如「形状」。它持有一个指向实现部分的引用/指针。
  • 实现部分(Implementation):可被替换的底层实现,比如「渲染方式」。它通常被定义成接口(抽象基类)。
  • 抽象部分通过组合(而非继承)调用实现部分,二者之间的连接被称为「桥」。

UML 结构如下:

桥接

Shape

-Renderer* renderer

+draw()

Circle

+draw()

Square

+draw()

Renderer

+render()

OpenGLRenderer

+render()

VulkanRenderer

+render()

这样一来,「形状」和「渲染方式」两个维度可以各自独立扩展。例如新增一种渲染方式时,只需新增一个 Renderer 子类,所有形状都能立即使用它,无需为每个形状重复派生子类。

3. C++ 案例:图形绘制系统

下面用一个完整可运行的 C++ 案例演示桥接模式。抽象部分为Shape(形状),实现部分为Renderer(渲染器),通过指针完成桥接。

3.1 实现部分:渲染器接口

// renderer.h#pragmaonce#include<iostream>#include<string>// 渲染器接口classRenderer{public:virtual~Renderer()=default;virtualvoidrender(conststd::string&shapeName)=0;};// 具体OpenGL 渲染器classOpenGLRenderer:publicRenderer{public:voidrender(conststd::string&shapeName)override{std::cout<<"使用 OpenGL 绘制 "<<shapeName<<std::endl;}};// 具体Vulkan 渲染器classVulkanRenderer:publicRenderer{public:voidrender(conststd::string&shapeName)override{std::cout<<"使用 Vulkan 绘制 "<<shapeName<<std::endl;}};

3.2 抽象部分:形状基类

// shape.h#pragmaonce#include<memory>#include<string>#include"renderer.h"// 抽象部分:形状基类,持有一个渲染器指针作为“桥”classShape{public:explicitShape(std::shared_ptr<Renderer>renderer):renderer_(std::move(renderer)){}virtual~Shape()=default;virtualvoiddraw()=0;protected:std::shared_ptr<Renderer>renderer_;};// 扩展抽象:圆形classCircle:publicShape{public:explicitCircle(std::shared_ptr<Renderer>renderer):Shape(std::move(renderer)){}voiddraw()override{renderer_->render("圆形");}};// 扩展抽象:方形classSquare:publicShape{public:explicitSquare(std::shared_ptr<Renderer>renderer):Shape(std::move(renderer)){}voiddraw()override{renderer_->render("方形");}};

3.3 客户端调用

// main.cpp#include"shape.h"#include"renderer.h"intmain(){// 创建不同的实现(渲染器)autoopengl=std::make_shared<OpenGLRenderer>();autovulkan=std::make_shared<VulkanRenderer>();// 组合抽象与实现:两个维度独立变化CirclecircleWithOpenGL(opengl);SquaresquareWithVulkan(vulkan);CirclecircleWithVulkan(vulkan);// 新增组合无需新增子类circleWithOpenGL.draw();squareWithVulkan.draw();circleWithVulkan.draw();return0;}

运行结果:

使用 OpenGL 绘制 圆形 使用 Vulkan 绘制 方形 使用 Vulkan 绘制 圆形

3.4 扩展验证:新增渲染方式与新增线条风格维度均零负担

先验证同维度扩展。假设现在要新增一种渲染方式DirectXRenderer,只需新增一个Renderer子类:

classDirectXRenderer:publicRenderer{public:voidrender(conststd::string&shapeName)override{std::cout<<"使用 DirectX 绘制 "<<shapeName<<std::endl;}};

所有形状(Circle、Square)无需任何改动即可搭配新渲染器使用。同理,新增一种形状也只需派生一个Shape子类,完全不影响渲染器体系。

新增一个独立的维度,若继续用继承,类数量会变成「形状 × 渲染方式 × 线条风格」的乘积爆炸;桥接模式则继续用组合拆解。

// line_style.h#pragmaonce#include<iostream>#include<string>// 线条风格接口classLineStyle{public:virtual~LineStyle()=default;virtualvoidapply(conststd::string&shapeName)=0;};// 具体线条风格:实线classSolidLineStyle:publicLineStyle{public:voidapply(conststd::string&shapeName)override{std::cout<<"使用实线风格绘制 "<<shapeName<<std::endl;}};// 具体线条风格:虚线classDashedLineStyle:publicLineStyle{public:voidapply(conststd::string&shapeName)override{std::cout<<"使用虚线风格绘制 "<<shapeName<<std::endl;}};

然后,在实现侧增加一个StyledRenderer,它本身仍然是Renderer,只是内部再组合「真正的渲染器」和「线条风格」:

// styled_renderer.h#pragmaonce#include<memory>#include"renderer.h"#include"line_style.h"// 实现侧扩展:为一个渲染器附加线条风格classStyledRenderer:publicRenderer{public:StyledRenderer(std::shared_ptr<Renderer>renderer,std::shared_ptr<LineStyle>style):renderer_(std::move(renderer)),style_(std::move(style)){}voidrender(conststd::string&shapeName)override{renderer_->render(shapeName);style_->apply(shapeName);}private:std::shared_ptr<Renderer>renderer_;std::shared_ptr<LineStyle>style_;};

此时抽象侧Shape、Circle、Square保持 3.2 中的原样,构造参数仍然只有一个Renderer,完全不用改动。客户端按需组合:

// main.cppautoopengl=std::make_shared<OpenGLRenderer>();autosolid=std::make_shared<SolidLineStyle>();//实线autodashed=std::make_shared<DashedLineStyle>();//虚线// 把「渲染方式 + 线条风格」组合成新的 RendererautoopenglSolid=std::make_shared<StyledRenderer>(opengl,solid);autoopenglDashed=std::make_shared<StyledRenderer>(opengl,dashed);// 抽象侧接口不变,仍然只接收 RendererCirclec1(openglSolid);Circlec2(openglDashed);Squares1(openglDashed);c1.draw();c2.draw();s1.draw();

运行结果:

使用 OpenGL 绘制 圆形 使用实线风格绘制 圆形 使用 OpenGL 绘制 圆形 使用虚线风格绘制 圆形 使用 OpenGL 绘制 方形 使用虚线风格绘制 方形

可以看到:新增「线条风格」维度时,抽象部分接口零修改,只在实现侧新增LineStyle接口与StyledRenderer组合类。形状、渲染方式、线条风格仍然自由组合,却没有为每个组合新建子类;类规模保持加法级增长,同时业务抽象Shape保持稳定。这才是桥接模式「独立变化、按需组合」的关键。

4. 小结

桥接模式通过「组合代替继承」,成功解耦了抽象与实现两个独立变化的维度,从根本上避免了子类组合爆炸。在 C++ 中通常借助抽象基类、虚函数以及智能指针实现这一桥接。当你的系统中存在两个或多个独立变化维度,且使用继承会导致类数量乘积增长时,就是引入桥接模式的合适时机。

返回列表