使用 Swift 开发应用时,总会遇到类不断增加的时刻。
明明只添加了一个按钮,却要分别支持浅色模式、深色模式、iOS 和 iPadOS;组合越多,类的数量就越容易爆炸。
先说结论。Swift 桥接模式将“做什么(抽象)”和“怎么做(实现)”分离到不同的类层次中,让两个维度通过相加而不是相乘来管理。
本文将依次说明子类爆炸为何发生、桥接模式如何避免,以及实际的 Swift 代码。
为什么会发生子类爆炸?
当两个相互独立的变化维度只能通过继承来表达时,就会发生子类爆炸。
来看一个例子。
假设有一个 Remote 类,可以用它控制电视和收音机。
如果还需要“基础遥控器”和“高级遥控器”两种类型呢?
组合会变成这样。
- 基础遥控器 × 电视
- 基础遥控器 × 收音机
- 高级遥控器 × 电视
- 高级遥控器 × 收音机
这就已经有 4 个了。
设备增加“音箱”后变成 6 个,遥控器增加“语音遥控器”后则变成 9 个。
当有两个维度时,只使用继承会让类的数量按乘法(m×n)增长,而不是按加法(m+n)增长。
这就是子类爆炸。维度越多,维护负担增长得越快。
Swift 桥接模式如何避免这个问题?
核心只有一个。
将两个变化维度分别拆成独立的层次,再通过引用(组合)连接起来。
- 抽象层:遥控器类型(基础/高级)
- 实现层:设备类型(电视/收音机)
遥控器不继承设备,而是将设备接口作为属性持有。
首先将实现层定义为协议,只包含设备必须提供的最小操作。
// 实现层:设备必须提供的最小功能
protocol Device {
var volume: Int { get set }
func enable()
func disable()
}
现在,作为抽象层的遥控器只引用 Device。这是注入,而不是继承。
// 抽象层:持有设备并委托给它
class RemoteControl {
let device: Device // 桥接连接点
init(device: Device) { self.device = device }
func togglePower() { /* device.enable/disable 调用 */ }
}
这个 device 属性就是连接两个层次的桥(Bridge)。
实际扩展后会有什么不同?
这是我亲自扩展代码时感受到的变化。
添加高级遥控器时,只需继承 RemoteControl,在遥控器层次中新增一个类即可。
// 扩展抽象:设备代码一行都不用改
class AdvancedRemote: RemoteControl {
func mute() {
var d = device
d.volume = 0 // 委托给实现层
}
}
反过来,添加新设备(例如音箱)时,只需遵循 Device 协议。遥控器代码可以原样复用。
两个维度完全独立,因此扩展一侧时不必重新创建另一侧的组合。
用表格比较,差异会很明显。
| 分类 | 仅使用继承 | 桥接模式 |
|---|---|---|
| 类的增长方式 | 乘法(m×n) | 加法(m+n) |
| 3 种遥控器 × 4 种设备 | 12 个 | 7 个 |
| 添加新设备时 | 按遥控器类型数量增加 | 只增加 1 个 |
| 运行时替换 | 困难 | 通过注入灵活替换 |
以 3 种遥控器和 4 种设备为例,类的数量从 12 个减少到 7 个。维度越多,差距越明显。
什么时候该用,什么时候该避免?
桥接模式并不是所有场景的答案。到处滥用反而容易让代码变复杂。
适合使用的情况:
- 存在两个或更多相互独立变化的维度
- 希望在运行时替换实现(例如真实 API ↔ 模拟对象)
- 希望清晰地拆分平台相关和主题相关的实现
可以不使用的情况:
- 只有一个维度,或组合今后也固定为 2~3 个
- 结构很简单,却提前拆分层次而造成过度设计
Swift 中协议和依赖注入本来就很自然,因此你可能只是叫不出桥接模式的名字,实际上早就在使用它。
常见问题(Q&A)
问:它和适配器模式有什么不同?
适配器用于事后连接已经创建但彼此不兼容的代码;桥接模式则是在设计阶段就将两个维度分开。
问:一定要使用 protocol 吗?
是的。在 Swift 中,将实现层定义为 protocol 最自然。使用协议而不是抽象类后,值类型(struct)也可以作为实现。
问:SwiftUI 中也能使用吗?
可以。如果视图通过注入接收数据源协议,只负责渲染,这就是桥接结构。
如果你一直因子类不断增加而感到烦恼,今天只需记住一件事。
当你觉得“这个类有两个变化原因”时,就是拆分两个维度的时机。
先亲手改写一个小例子,你会比想象中更快熟悉。希望你的代码变得更轻量!

