🧠 设计动机:解决传统 OOP 的痛点
传统面向对象编程在很多大型工程里会遇到这些问题:
-
继承树膨胀、僵化
- 类层级一旦设计,就难以改动。
- 多重行为只能靠继承链或组合,复杂且难维护。
-
代码复用靠父类、造成耦合
- 子类必须继承父类才能复用逻辑。
- 改变父类可能影响整个继承树。
-
“能力”分散、难组合
- 一个对象如果要同时具备多种行为(比如飞行、游泳),单纯继承不容易表达。
Swift 的核心价值:
提倡用能力(行为)来抽象,而不是用身份(类型层次)来组织代码。
这也是为什么 Swift 早期标准库大量使用协议的原因。
🛠 核心理念:协议定义行为,结构体/类组合这些行为
一个对象 能做什么 —— 用协议表示
一个对象是什么 —— 协议组合 + 类型实现
这种方式相比传统的 OOP,在 解耦、复用、可测试性 上更优雅。
🚀 实际案例对比
我们用一个具体例子来说明:
场景:实现一个通用的“可缓存对象”,支持不同策略(LRU / 带过期时间),并且可以注入到网络层进行缓存策略替换。
❌ 传统 OOP 方案(继承)
// 父类抽象缓存
class Cache {
func get(key: String) -> Any? { return nil }
func set(key: String, value: Any) {}
}
// LRU 缓存子类
class LRUCache: Cache {
override func get(key: String) -> Any? { … }
override func set(key: String, value: Any) { … }
}
// 带过期策略缓存
class ExpiringCache: Cache {
var expirationTime: TimeInterval
init(expirationTime: TimeInterval) {
self.expirationTime = expirationTime
}
override func get(key: String) -> Any? { … }
override func set(key: String, value: Any) { … }
}
问题:
✔️ 每种策略必须继承 Cache
✔️ 缓存行为耦合,难组合策略
❌ 没法复用“过期逻辑”给其它类型
❌ 难以创建“LRU + 过期”组合
✅ Swift 协议导向方案(POP)
// 行为协议
protocol Cacheable {
associatedtype Value
func get(_ key: String) -> Value?
func set(_ key: String, _ value: Value)
}
// 默认实现可以用协议扩展
extension Cacheable {
func logAccess(_ key: String) {
print("[Cache] access (key)")
}
}
// LRU策略能力
protocol LRUCacheable: Cacheable {}
extension LRUCacheable {
func lruEvictIfNeeded() { … }
}
// 过期策略能力
protocol Expirable {
var expirationTime: TimeInterval { get }
func isExpired(_ key: String) -> Bool
}
// 组合策略对象
struct LRUCacheWithExpiry<Value>: LRUCacheable, Expirable {
var expirationTime: TimeInterval
private var storage: [String: Value] = [:]
func get(_ key: String) -> Value? {
guard !isExpired(key) else { return nil }
return storage[key]
}
func set(_ key: String, _ value: Value) {
storage[key] = value
}
func isExpired(_ key: String) -> Bool {
// expiration logic…
}
}
你会发现:
✔️ 行为是组合的(而非继承的)
✔️ “过期能力”可以组合到其他缓存策略
✔️ 支持策略插拔(注入到网络层)
✔️ 易测试(只依赖协议)
🧩 核心好处总结
✅ 更低耦合
协议之间是 契约,不是继承链;改动一个不用影响其他。
func fetchData<C: Cacheable>(from url: URL, cache: C) { … }
任何实现了 Cacheable 的类型都可以传入。
✅ 行为组合更自由
可以创建:
- LRU + 过期
- 过期 + TTL
- 仅 LRU
- 仅 TTL
不需要层层继承。
✅ 易测试、易 mock
protocol NetworkSession {
func request(_ url: URL, completion: (Data?) -> Void)
}
struct MockSession: NetworkSession {
func request(_ url: URL, completion: (Data?) -> Void) {
completion(Data()) // mock
}
}
只要实现协议,就可替换依赖。
✅ 更自然契合 Swift 标准库设计
举例来说:
extension Array: RangeReplaceableCollection {}
就是用协议组合提供行为,而不是让 Array 继承一大堆类。
🟡 什么时候还适合用类继承?
Swift 并不是完全否定面向对象,继承仍然有价值:
✅ UIKit / AppKit 这种 UI 组件树
✅ 需要引用语义的情况(shared mutable state)
✅ 框架中有明确层级逻辑
但这种继承更多是 实现细节,不再用于 行为抽象。
🏁 总结
| 面向对象 | 面向协议 |
|---|---|
| 重视类型层次结构 | 重视行为(能力)抽象 |
| 继承重用逻辑 | 协议扩展默认逻辑复用 |
| “你是什么?” | “你能做什么?” |
| 容易耦合、难组合 | 易组合、易测试、解耦强 |
Swift 团队提出 “面向协议优先于面向对象” 的原因是:
👉 协议提供更强的模块化与行为组合能力,从而提升代码灵活性、可测试性与可复用性。