目录
- 定义
- 核心问题
- 如何判断哪条是抽象链
- 使用场景
- 参与者
- 优劣
- 协作
- 实现
- 相关模式
- 应用和思考
定义
桥接模式是将抽象部分和它的实现部分分离,使他们都可以独立的变化
核心问题
解决类继承的维度爆炸
当一个类型有两个独立变化的维度时,把两个维度都塞进继承树是灾难。通过下面例子说明:遥控器 x 电视品牌
- 维度1(遥控器):你手里的按键语义——基础款几个键、高级款多静音键、音量步进更细
- 维度2(电视):命令被真正执行的品牌协议——同样是"音量+",索尼和三星的红外编码完全不同,换台电视整套换。
关键在于两层互相不认识:按键语义不需要知道红外编码,红外编码也不关心你按的是哪个键。
```text 继承方案(专用遥控器爆炸): 桥接方案: Remote Remote ◀── Basic/Advanced ←维度1├── SonyBasicRemote │ 持有 tv_(唯一一条桥) ├── SonyAdvancedRemote ▼ ├── SamsungBasicRemote Tv ◀── Sony/Samsung/Virtual ←维度2└── SamsungAdvancedRemote- n款显示器 x m个品牌:继承要n x m个专用型号,桥接模式下只需要n + m个类
- 接入一个新品牌(小米/华为):继承要把遥控器系全部重做(再加n个型号);桥接加1个协议类
- 更隐蔽的害处:继承方案里
SonyAdvancedRemote这种类名本身就是两个维度相乘的记号——看到"类名 = 维度1×维度2"就该条件反射想到桥接。
| 继承(专用型号) | 桥接 | |
|---|---|---|
| 加品牌前(2×2) | 4 个叶子 | 2 + 2 = 4 个具体类 |
| 加华为、小米后 | 2×4 = 8 叶(+4 类) | 2 + 4 = 6 类(+2 类) |
| 华为协议要写几遍 | 2 遍(改协议改两处) | 1 遍(两款遥控器免费复用) |
| 遥控器侧改动 | 4 个新类 | 0 |
| 再加第 3 档遥控器 | 再加 4 个类 | 再加 1 个类 |
“桥”的精确含义:两条继承链(抽象链,实现链)之间,原本需要 n x m条继承边连接;桥接后只剩一条组合边。
- 两条继承链并没有被消灭,而是保留且各自继续生长。bridge干掉的是错误的结构:用一棵继承树同时表达两个维度,把跨维度的那条边从“is-a"改成"has-a"
- 不只是静态结构重构,若把bridge只理解为整理继承关系,会漏掉组合带来的动态福利:
- 运行期换bridge
- 编译防火墙
- 实现链对抽象链的单向无知(依赖倒置)
适用边界
- 前提是两个维度正交(可自由组合),若有配套约束,该换抽象工厂
- k个维度就是k个bridge,原理不变
Bridge就是把维度相乘的一棵继承树重构为维度相加的多棵独立继承树+一条组合边;继承没有消失,只是退回到它擅长的领域,跨维度的耦合交给了组合
如何判断哪条是抽象链
| 判据 | 抽象链(Abstraction) | 实现链(Implementor) |
|---|---|---|
| 词汇表 | 业务名词:Remote、Telemetry——“这是什么” | 机制名词:Tv、Transport——“用什么做” |
| 客户可见性 | 客户代码直接使用 | 仅组装根可见,客户代码零出现 |
| 变化驱动力 | 业务需求(加一款遥控器/遥测源) | 技术选型(换品牌/换 DDS) |
| 持有桥 | ✓ 基类持有Implementor*成员 | ✗根本不知道抽象链存在 |
| 接口风格 | 意图级命令:volume_up()、report(v) | 原语级操作:set_volume(p)、encode(what,v) |
最硬的一条:依赖方向。桥的箭头永远从抽象指向实现
使用场景
- 不希望在抽象和他的实现部分之间有一个固定的绑定关系,这种情况可能是因为,在程序运行时刻,实现部分应可以被选择或者切换
- 抽象和实现都可以通过生成子类的方式加以扩充,这时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对他们进行扩充
- 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译(对抽象的实现进行修改,不需要桥接模式就可以实现?)
- 想对客户完全隐藏抽象的实现部分。在C++中类的表示在类接口中是可见的
- 有许多类要生成,这样的一种类层次结构说明必须将一个对象分解成两部分
- 想在多个对象间共享实现,但同时要求客户不知道这一点——使用场景
- 如果想拆分或重组一个具有多重功能的庞杂类,可以使用桥接模式
- 如果希望在几个独立维度上扩展一个类,可以使用该模式
参与者
- Abstraction:
- 定义抽象类接口
- 维护一个指向Implementor类型对象的指针
- RefeinedAbstraction:扩充由Abstraction定义的接口
- Implementor:定义实现类的接口,这些接口不一定要与Abstraction一致,事实上可以完全不同。一般来说Implementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作
- ConcreImplementor:实现Implementor接口并定义它的具体实现
优劣
- 优点
- 两个维度独立扩展互不影响(开闭原则)
- 运行期换实现
- 抽象层与实现层编译隔离
- 消除组合爆炸
- 缺点
- 多一层间接多一层转发
- 需要预先识别出那两个维度会变,识别错了维度,bridge就是白搭
- 小系统上直接 ** n x m ** 个类反而更加直白
协作
Abstraction将client的请求转发给它的Implementor对象
实现
- 仅有一个Implementor:在仅有一个实现的时候,没有必要创建一个抽象的Implementor
- 创建正确的Implementor对象:当存在多个Implementor类的时候,应该用何种方法,在何时何地创建哪个Implementor对象
- 共享Implementor对象
- 采用多重继承机制
- 怎样将正确的Implementor对象传给Abstraction对象?怎样使用抽象工厂来创建和配置一个特定的Bridge模式
相关模式
- 抽象工厂可以用来创建和配置一个特定的Bridge模式
- Adapter模式用来帮助无关的类协同工作,他通常在系统设计完成后才会被使用,而Bridge模式在系统开始时就被使用,它使得抽象接口和实现部分可以独立进行改变