协议组合 (Protocol Composition) 和 协议继承 (Protocol Inheritance) 就像是“临时组队”与“家族传承”的区别。虽然它们最终都能让你同时拥有多个协议的能力,但在设计意图和类型约束上有本质差异。
1. 协议继承 (Protocol Inheritance)
协议继承是静态的、永久的。它在定义协议时就确定了层级关系。
-
定义方式:
protocol C: A, B {} -
本质:创建一个新的协议。任何遵守
C的类型,必须同时实现A和B的要求。 -
使用场景:
- 建立层级:当一个概念是另一个概念的子集或扩展时(例如
User继承自Person)。 - 功能累加:你希望为这一组功能起一个有意义的“名字”。
- 建立层级:当一个概念是另一个概念的子集或扩展时(例如
Swift
protocol Readable { func read() }
protocol Writable { func write() }
// 协议继承:定义了一个新身份
protocol CodableStream: Readable, Writable { }
struct File: CodableStream {
func read() { /*...*/ }
func write() { /*...*/ }
}
2. 协议组合 (Protocol Composition)
协议组合是动态的、临时的。它不创建新类型,只是一种类型约束。
-
定义方式:
Readable & Writable -
本质:一种匿名协议簇。它告诉编译器:“我不在乎你是什么类型,只要你同时满足这两个协议就行。”
-
使用场景:
- 函数参数:限制参数必须具备多种能力。
- 临时约束:不需要为了一个简单的组合而专门定义一个庞大的协议层级。
Swift
// 协议组合:没有新名字,只是临时的约束
func syncData(source: Readable & Writable) {
source.read()
source.write()
}
3. 核心区别对比
| 特性 | 协议继承 (protocol C: A, B) | 协议组合 (A & B) |
|---|---|---|
| 产物 | 创建了一个新的命名类型 C。 | 产生一个匿名的复合类型。 |
| 耦合度 | 高。修改父协议可能影响整个继承树。 | 低。随用随组,不改变原协议结构。 |
| 身份 (Identity) | 类型必须明确声明遵守 C。 | 类型只要分别遵守了 A 和 B 即可。 |
| 扩展性 | 可以为 C 增加额外的方法要求。 | 无法在组合中增加新要求,只能用已有的。 |
| 语义 | 表达 "Is-a" 关系(是一个...)。 | 表达 "Acts-like" 关系(表现得像...)。 |
4. 什么时候用哪个?
选协议继承的情况:
当你发现某个组合在你的代码库中反复出现,且它代表了一个独立的业务概念。
例如:
Swift.Codable实际上就是Decodable & Encodable的继承。因为它太常用了,给它一个名字能极大地提高可读性。
选协议组合的情况:
当你在编写泛型工具函数或 UI 组件,且约束是高度特定于该场景时。
例如:一个保存按钮只需要一个
Encodable & Validatable的对象。你没必要为此专门定义一个protocol ValidatableEncodable。
5. 进阶:组合中的 AnyObject
你经常会在协议组合中看到 AnyObject。这通常用于将协议约束限制在类 (Class) 上,常用于 delegate 模式以避免循环引用。
Swift
// 这要求对象既是类,又遵守该协议
weak var delegate: (MyDelegate & AnyObject)?