直接继承会导致类爆炸,因为每增加一个图形类型与渲染器组合都需要创建新类,如3种图形×4种渲染器需12个类,且修改渲染逻辑须遍历所有子类;桥接模式通过抽象类(shape)持有一个渲染器指针,实现运行时动态替换,使图形与渲染器独立变化,避免多重继承语义错误和编译期绑定僵化。

为什么直接继承会导致类爆炸?
当你写 Circle 和 Rectangle,又想支持 OpenGLRenderer 与 DirectXRenderer,若用继承:每加一个图形 × 每加一个渲染器 = 新类。加 3 种图形、4 种渲染器,就得写 12 个类——而且改一个渲染逻辑,要动全部子类。
桥接模式不靠 class RedCircle : public Circle, public OpenGL 这种多重继承,而是让抽象(如 Shape)持有一个 std::shared_ptr<renderer></renderer>。这样图形增删不影响渲染器,渲染器升级也不用碰图形代码。
- 继承绑定是静态的,编译期就锁死;桥接是运行时组合,可动态替换
-
Shape构造时传入不同Renderer实例,同一Circle对象能先画 OpenGL,再换 DirectX - 避免
RedCircle继承Red的语义错误——矩形不是“一种红色”,而是“用红色渲染的矩形”
Abstraction 和 Implementor 的接口怎么设计才不耦合?
关键在「谁定义函数签名,谁负责实现」。抽象层(Shape)只暴露业务语义接口,比如 draw()、resize(double);实现层(Renderer)只暴露底层能力接口,比如 renderCircle(double x, double y, double r)、setPenColor(int r, int g, int b)。
两者之间不能出现跨层数据结构(如 Shape 里写 glDrawArrays 调用),也不能让 Renderer 知道自己在画圆还是画矩形。
-
Shape::draw()内部调用_renderer->renderCircle(_x, _y, _r),但不关心 _renderer 是 OpenGL 还是 Vulkan -
Renderer接口必须足够通用:不能叫drawWithGL,而应叫renderCircle,否则就泄露了实现细节 - 如果后续要支持抗锯齿开关,应在
Renderer接口加enableAntialiasing(bool),而不是在每个Shape子类里加 if 分支
std::shared_ptr 还是 raw pointer?析构谁负责?
用 std::shared_ptr 是最稳妥的选择。它明确表达了所有权共享意图,且避免手动 delete 导致的 double-free 或内存泄漏。
常见错误是把 Renderer* 传进 Shape 后,在 Shape::~Shape() 里 delete _impl —— 但客户端可能还拿着这个 Renderer 实例做别的事,一删就崩。
- 推荐构造方式:
Shape(std::shared_ptr<renderer> r) : _renderer(std::move(r)) {}</renderer> - 不要在
Shape中定义析构函数去释放_renderer;交给shared_ptr自动管理 - 若需独占所有权(极少数场景),可用
std::unique_ptr,但此时Shape必须声明为不可拷贝,并显式禁用operator= - 绝对避免裸指针 + 手动
new/delete混搭:一旦异常抛出,delete就漏了
RefinedAbstraction 怎么扩展才不破坏桥接结构?
RefinedAbstraction(比如 FilledShape 或 AnimatedShape)应该只增强抽象行为,绝不侵入实现细节。它的所有新功能必须通过委托给 _renderer 完成,或复用已有 Shape 接口。
例如:想给图形加描边,不要在 FilledCircle 里直接调 glLineWidth;而应让 Renderer 提供 setStrokeWidth(double),然后由 FilledShape::draw() 先设宽度、再调 _renderer->renderCircle()。
- 新增抽象行为(如动画)应封装在
RefinedAbstraction内部状态中,比如double _animationProgress - 所有对渲染的调用,仍必须经过
_renderer接口,保证实现层可随时替换 - 若某扩展需要新渲染能力(如阴影),必须先扩
Renderer接口,再在RefinedAbstraction中使用——顺序不能反
桥接模式最难的不是写代码,而是判断「哪部分该放进 Abstraction,哪部分该放进 Implementor」。一个简单检验:如果某个逻辑变化会导致多个 Renderer 都要改,那它大概率属于 Abstraction 层;反之,如果只影响一种平台(比如仅 OpenGL 需要 VAO 绑定),那就该收进 ConcreteImplementor 里。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











