一、一句话总原则(先记住)
凡是你希望“可以被不同类型定制 / override”的行为,
必须写进protocol,而不是只写在protocol extension里。
这句话几乎能解决 80% 的坑。
二、问题根源回顾(为什么会出问题)
protocol Cache {}
extension Cache {
func clear() {
print("default clear")
}
}
struct MemoryCache: Cache {
func clear() {
print("memory clear")
}
}
let c: Cache = MemoryCache()
c.clear() // ❗ default clear
原因只有一个:
clear()不是协议要求
👉 不进 witness table
👉 编译期静态绑定 extension 实现
三、7 种工程级避免方案(从强到弱)
下面是你在真实项目中 可以直接用的做法。
✅ 方案 1(最推荐):方法必须声明在 protocol 中
这是唯一真正支持多态的方式。
protocol Cache {
func clear()
}
extension Cache {
func clear() {
print("default clear")
}
}
✔️ extension 提供默认实现
✔️ conforming type 可 override
✔️ protocol 调用走 witness table
📌 这是 Swift 团队设计 POP 的“正统用法”
✅ 方案 2:用 @available(*, unavailable) 防误用
如果你不希望别人错误地在 extension 里“覆盖” :
protocol Cache {
func clear()
}
extension Cache {
@available(*, unavailable)
func clear() {}
}
强制要求实现者自己写,防踩坑(库作者常用)。
✅ 方案 3:extension 里只写「模板方法」
把可变部分交给 protocol 要求。
protocol Cache {
func removeAll()
}
extension Cache {
func clear() {
print("log clear")
removeAll()
}
}
✔️ clear() 是静态派发
✔️ 但真正的差异在 removeAll()
✔️ 多态依然成立
📌 这是最优雅的 POP 组合方式之一
✅ 方案 4:明确区分“多态 API”和“辅助 API”
命名 + 文档约束:
protocol Cache {
func clear() // 多态
}
extension Cache {
func clearWithLog() // 工具方法
}
📌 让 extension 方法“不长得像 override 点”
⚠️ 方案 5:用泛型而不是 existential(间接避免)
func clearCache<C: Cache>(_ cache: C) {
cache.clear()
}
即便有默认实现,泛型上下文仍然可能静态绑定具体实现。
但注意:
- 不能修复“协议没声明方法”的问题
- 只是减少 existential 使用
⚠️ 方案 6:用 class + 继承(仅限需要引用语义)
class Cache {
func clear() {}
}
class MemoryCache: Cache {
override func clear() {}
}
适用于:
- UIKit / AppKit
- 需要 shared mutable state
- 框架设计
📌 不是 POP 的主流解法,但现实中有效
🚫 方案 7(不要用):靠“记住规则”
“大家记得不要在 extension 里 override 就好”
❌ 不可规模化
❌ 团队必翻车
四、一个“黄金设计模板”(非常实用)
🔒 稳定多态接口
protocol Renderer {
func render()
}
🧱 默认行为(extension)
extension Renderer {
func render() {
prepare()
draw()
}
func prepare() {
print("default prepare")
}
func draw() {
fatalError("must implement")
}
}
🎯 自定义点(协议要求)
struct OpenGLRenderer: Renderer {
func draw() {
print("OpenGL draw")
}
}
📌 多态只发生在 明确的协议要求点
五、团队级 Code Review 检查清单(直接用)
每看到一个 protocol extension,问 4 个问题:
- 这个方法 需要被 override 吗?
- 如果需要,它 是否声明在 protocol 中?
- extension 方法是否只是 模板 / 工具 / 派生行为?
- 是否有命名 / 文档明确区分?
如果 1️⃣ = YES,2️⃣ = NO → 必改
六、一句话终极总结(刻在脑子里)
extension 里的默认实现是“复用工具”,不是“多态入口”。
多态入口必须写在 protocol 本体中。