- 示例工程
【免费下载链接】Unity3DTraining
【Unity杂货铺】unity大杂烩~
桥接模式(Bridge Pattern)是结构型设计模式中的经典一员,它的核心是把"抽象的部分"与"实现的部分"分离,让两者都能独立变化。本文以仓库 DesignPatterns/BridgePattern 目录下的 README 文档为主体,结合其中完整的 C# 控制台示例源码,讲解桥接模式的定义、适用场景、经典结构,并给出可运行、可验证的选课系统改造代码。读完本文,你将掌握桥接模式的判定方法、从"继承爆炸"到"桥接解耦"的重构思路,以及 Abstraction / Implementor 两组层次在运行时如何自由组合。
桥接模式的定义:抽象与实现分离
原文档给出了一个非常精炼的定义:
将抽象的部分与它的实现部分分离,使他们都可以独立地变化。
这里的"实现"指的是什么?文档给出了关键注解:实现指的就是抽象类和它的派生类用来实现自己的对象。换句话说,抽象类本身并不直接完成全部工作,而是把部分(甚至全部)职责委托给另一个独立的对象去完成,这个被委托的对象就是"实现"。
为什么需要这种分离?文档用一个非常生活化的场景来解释:课程既可以按照课程类别来区分(数学类、计算机类……),也可以按照系所来区分(计算机系、数学系……)。当业务中存在多种分类维度、且每种分类都需要独立变化时,就特别适合使用桥接模式。
也就是说,桥接模式要解决的核心问题不是"某个类怎么实现",而是"多个维度同时变化时,如何避免维度之间的耦合"。
问题背景:多维分类导致的继承爆炸
在没有桥接模式之前,面对"课程按类别分 × 按系所分"这样的二维分类需求,最直觉的做法是用继承层层派生。但这样会很快陷入"类爆炸"的泥潭:
- 每新增一个课程类别,就要为每一个系所各写一个子类;
- 每新增一个系所,又要为每一个课程类别各写一个子类;
- 类别数 × 系所数 = 子类总数,增长是乘法级别的;
- 一旦课程类别的行为发生变化,所有相关子类都要跟着改,耦合度极高。
文档明确指出,当代码完成桥接改造之后,无论是新加一门数学类的课程,还是新加一个需要选课的系所,都只需要新增一个子类即可:
- 新加课程:只需新增一个
Math的子类; - 新加系所:只需新增一个
Departments的子类。
这样不仅降低了工作量,还解决了使用继承带来的高耦合性问题,并且符合开闭原则(Open-Closed Principle,对扩展开放、对修改关闭)。
仓库实战:选课系统的桥接改造
DesignPatterns/BridgePattern 目录下的BridgePattern是一个 .NET Framework 4.5.2 的控制台工程(见 BridgePattern.csproj,OutputType为Exe、TargetFrameworkVersion为v4.5.2)。工程里同时演示了两部分内容:选课系统示例与经典桥接结构示例。
第一组层次:系所抽象(Departments)
Departments.cs 定义了系所这一维度。抽象类Departments持有一个课程对象mathCourse,通过SetCourse方法注入(这正是"桥"的关键:系所不关心课程具体是哪一个,只关心它有Select()能力),并声明了抽象的选课行为Select():
/// <summary> /// 系所抽象类代码 /// </summary> abstract class Departments { //系所包含的课程对象 protected Math mathCourse; /// <summary> /// 设置课程 /// </summary> /// <param name="math"></param> public void SetCourse(Math math) { this.mathCourse = math; } /// <summary> /// 选择课程 /// </summary> public abstract void Select(); } //具体的系所代码 /// <summary> /// 计算机系 /// </summary> class Computer : Departments { public override void Select() { Console.WriteLine("计算机专业同学选课"); mathCourse.Select(); } } class Mathematics : Departments { public override void Select() { Console.WriteLine("数学系的同学选课"); mathCourse.Select(); } }注意这里的委托关系:Computer.Select()和Mathematics.Select()都只负责输出"谁在选课",然后把真正的选课动作转发给mathCourse.Select()。系所自身不实现任何具体课程逻辑,这正是"抽象与实现分离"的体现。
第二组层次:课程抽象(Math)
Math.cs 定义了课程这一维度。抽象类Math声明抽象的Select(),其下是各个具体课程:
//数学课程 abstract class Math { public abstract void Select(); } //数学课程的具体类代码: /// <summary> /// 数学分析 /// </summary> class MathAnalysis : Math { public override void Select() { Console.WriteLine("选择了数学分析"); } } class AdvanceMath : Math { public override void Select() { Console.WriteLine("选择了高等数学"); } }至此,两个分类维度被拆成了两棵独立的继承树:
Departments(抽象)→Computer/Mathematics(具体系所);Math(抽象)→MathAnalysis/AdvanceMath(具体课程)。
它们之间唯一的连接点是Departments中的protected Math mathCourse字段,通过SetCourse()在运行时注入——这个连接点就是文档所说的"桥"。
运行入口与可验证输出
Program.cs 演示了运行时如何自由组合两个维度:
static void Main(string[] args) { //举例 Departments dp; dp = new Computer(); dp.SetCourse(new AdvanceMath()); dp.Select(); dp = new Mathematics(); dp.SetCourse(new MathAnalysis()); dp.Select(); //桥接模式 Abstraction abstraction = new RedefineAbstraction(); abstraction.SetImplementor(new ConcreteImplementorA()); abstraction.Operation(); abstraction.SetImplementor(new ConcreteImplementorB()); abstraction.Operation(); }依据源码逐行推演,控制台输出依次为:
计算机专业同学选课 选择了高等数学 数学系的同学选课 选择了数学分析 A的具体操作 B的具体操作可以看到:同一个系所(Computer)可以搭配不同的课程,同一个课程也可以被不同系所选择。维度之间的组合是在运行时由调用方决定的,而不是在编译期通过继承固定死的。新增一门课程(如"线性代数")只需新增一个Math子类,所有系所无需改动;新增一个系所同理。这就是文档所说的"独立变化"。
经典桥接结构:Abstraction 与 Implementor
除了选课示例,仓库还提供了桥接模式的经典骨架,也就是设计模式教材中最标准的四角色结构。
Implementor:实现部分的抽象
Implementor.cs 定义"实现部分"的抽象接口及其两个具体实现:
abstract class Implementor { public abstract void Operation(); } class ConcreteImplementorA : Implementor { public override void Operation() { Console.WriteLine("A的具体操作"); } } class ConcreteImplementorB : Implementor { public override void Operation() { Console.WriteLine("B的具体操作"); } }Abstraction:抽象部分的骨架
Abstraction.cs 定义"抽象部分"的骨架。抽象类持有一个Implementor引用,通过SetImplementor()注入,然后由具体的抽象子类把Operation()转发给实现对象:
abstract class Abstraction { protected Implementor implementor; public void SetImplementor(Implementor implementor) { this.implementor = implementor; } public abstract void Operation(); } class RedefineAbstraction : Abstraction { public override void Operation() { implementor.Operation(); } }RedefineAbstraction是"精化抽象"(Refined Abstraction):它不关心implementor具体是 A 还是 B,只负责把调用转发下去。这样,Abstraction这一维与Implementor这一维就彻底解耦了,任何一维扩展出新子类,都不影响另一维。
与选课示例的对应关系
把经典结构套回选课场景,对应关系一目了然:
| 经典角色 | 选课示例 |
|---|---|
| Abstraction(抽象) | Departments(系所) |
| RefinedAbstraction(精化抽象) | Computer/Mathematics |
| Implementor(实现接口) | Math(课程) |
| ConcreteImplementor(具体实现) | MathAnalysis/AdvanceMath |
| 桥(Bridge) | Departments.mathCourse字段 +SetCourse()注入 |
从这个对应表可以看出,选课示例完全遵循了桥接模式的经典骨架,只是换上了业务语义更强的类名。
桥接模式的适用场景
原文档给出了三条明确的适用判断标准,这也是判断"要不要用桥接模式"的黄金准则:
- 当不希望在抽象和它的实现部分之间有一个固定的绑定关系时。桥接模式允许在运行时通过注入来改变绑定,而不是在编译期用继承写死。
- 当类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充时。两棵继承树各自独立扩展,互不干扰。
- 当对一个抽象类的实现部分的修改应对客户不产生影响时(客户的代码不需要重新编译)。因为客户只面向抽象接口编程,实现细节被隔离在另一棵继承树中。
文档还从"抽象方法"的视角给出了一个更深层的推导:某个抽象是一个类,那么它必依赖于抽象方法;抽象的层次结构中,父类的具体方法必然依赖于其他抽象方法。当我们沿着原层次结构按另一种方向继续派生子类时,就不得不把这些抽象方法也一并移植到其他层级结构中去。此时就可以使用桥接模式——将一个抽象与这个抽象中所提供的抽象方法的实现互相分离。
用更通俗的话说:当你发现"抽象层次结构"和"它依赖的抽象方法实现"必须朝两个方向同时生长时,把它们拆成两棵独立变化的树,再用一个对象引用把两者桥接起来,就是桥接模式的正确用法。
小结:桥接模式带来的独立变化
文档的小结部分把桥接模式的效果总结得非常到位:桥接模式从效果上解除了不同分类的实现之间的耦合性,使其可以独立地变化。
仍以选课代码为例:课程与系所由于耦合性降低,使得——
- 增加课程时,不会影响系所的代码:新增
Math子类即可,Departments一族零改动; - 增加系所时,也不会影响课程的代码:新增
Departments子类即可,Math一族零改动。
这就是"独立变化"的确切含义,也是开闭原则在结构层面的落地。相比继承带来的乘法级类数量,桥接把复杂度从"组合类数量"降低为"两个维度类数量的加法",代价只是抽象接口上多一次运行时注入。
从控制台示例到 Unity 游戏开发
虽然本示例是一个脱离引擎的纯 C# 控制台工程,但桥接模式在游戏开发中的价值同样直接。从行业实践看,桥接模式常见于这些场景:将"渲染 API 抽象"(如 OpenGL / DirectX / Vulkan 的不同实现)与"具体渲染对象"分离;将"输入设备差异"与"游戏逻辑层"分离;将"不同平台的商店/支付 SDK"与"统一的业务接口"分离。这些场景的共同特征与本例完全一致:业务维度与平台/实现维度各自独立变化。
仓库中的设计模式索引 DesignPatterns/README.md 将桥接模式与其他 23 种模式并列收录,是 Unity3DTraining 项目"Unity 杂货铺"体系的一部分;若想进一步了解游戏领域特有的模式,可继续阅读 游戏编程模式。
快速上手:如何运行本示例
- 使用 Visual Studio(或 Rider / MonoDevelop)打开 BridgePattern.csproj;
- 工程为 .NET Framework 4.5.2 控制台程序,直接运行(F5)即可看到上述输出;
- 想验证"独立变化"的效果,可以仿照现有代码新增一个
Math子类(例如"线性代数")并在Main中与Computer组合,无需改动任何系所类即可编译运行。
文中引用的全部源码均位于 BridgePattern 工程目录,原始讲解文档为 BridgePattern/README.md,读者可对照阅读,逐行印证桥接模式从定义到落地的完整过程。
- 示例工程
【免费下载链接】Unity3DTraining
【Unity杂货铺】unity大杂烩~
相关推荐
CS-Notes 设计模式精讲:桥接(Bridge)模式——把抽象与实现分开,让两者独立演进
CS Notes 设计模式精讲:桥接(Bridge)模式——把抽象与实现分开,让两者独立演进 本篇文章是 CS Notes 设计模式系列中「桥接(Bridge)
知识库文档教程DyberPet桥接模式:抽象与实现分离设计
DyberPet桥接模式:抽象与实现分离设计 在桌面宠物框架DyberPet中,桥接模式(Bridge Pattern)作为核心设计思想,实现了抽象层与实现层的
桌面应用AI 应用交互助手别再只"会用"了:Dear ImGui 即时模式 GUI 从原理到落地的快速上手
别再只"会用"了:Dear ImGui 即时模式 GUI 从原理到落地的快速上手 刚拿到 Dear ImGui 源码,编译通过了,可窗口一出来还是空白、中文字符
UI组件前端桌面应用图形学
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考