4-14.【协议导向编程】举例说明 default implementation 导致 bug 的场景,以及如何规避。

2 阅读2分钟

一、场景说明:默认实现破坏多态

背景

假设你在写一个 缓存协议 Cache,希望:

  • 提供默认实现 clear()
  • 不同缓存类型(MemoryCache / DiskCache)可以覆盖

1️⃣ 协议 + 默认实现(错误写法)

protocol Cache {}

extension Cache {
    func clear() {
        print("default clear")
    }
}

struct MemoryCache: Cache {
    func clear() {
        print("MemoryCache cleared")
    }
}

struct DiskCache: Cache {
    func clear() {
        print("DiskCache cleared")
    }
}

2️⃣ 不同调用方式导致的 bug

let memCache = MemoryCache()
memCache.clear()
// 输出: MemoryCache cleared ✅

let cache: Cache = MemoryCache()
cache.clear()
// 输出: default clear ❌ 预期 MemoryCache cleared

原因

  1. clear() 没有在 protocol 中声明 → 不进入 witness table
  2. protocol 类型调用时走 静态派发 → 调用 extension 默认实现
  3. MemoryCache 的实现被忽略 → 多态失效

🔥 这就是典型的默认实现导致 bug的场景。


二、规避策略

✅ 1️⃣ 在 protocol 中声明方法(标准做法)

protocol Cache {
    func clear()  // 声明多态点
}

extension Cache {
    func clear() {  // 默认实现
        print("default clear")
    }
}

struct MemoryCache: Cache {
    func clear() {  // 覆盖默认实现
        print("MemoryCache cleared")
    }
}

struct DiskCache: Cache {
    func clear() {
        print("DiskCache cleared")
    }
}

let cache: Cache = MemoryCache()
cache.clear()  
// 输出: MemoryCache cleared ✅
  • 核心:多态点必须在协议中声明
  • 默认实现仍然保留 → 类型可以选择使用或覆盖

✅ 2️⃣ 对于不想覆盖的方法 → 只写 extension(辅助工具)

protocol Cache {
    func clear()
}

extension Cache {
    func logClear() {  // 辅助方法
        print("Clearing cache...")
    }
}
  • logClear() 不在 protocol 声明中 → 静态派发
  • 只做工具函数,不破坏多态

✅ 3️⃣ 多协议继承时谨慎使用默认实现

protocol Logger {}
protocol FileLogger: Logger {}

extension Logger {
    func log() { print("Logger log") }
}

extension FileLogger {
    func log() { print("FileLogger log") }
}

struct MyLogger: FileLogger {}

let l1: Logger = MyLogger()
l1.log() // Logger log
let l2: FileLogger = MyLogger()
l2.log() // FileLogger log

⚠️ 同一个对象,输出不同 → 静态派发陷阱

规避

  • log() 声明在协议中
  • extension 只提供默认实现
  • conforming 类型覆盖 → 多态恢复

三、总结工程级经验

场景问题解决方案
默认实现不在协议中protocol 类型调用失效 → 多态被破坏把方法声明在 protocol 中
多协议 extension 同名方法静态派发导致不同输出避免同名方法,或明确协议声明 + 默认实现
默认实现覆盖不了类型自实现不是 bug,而是静态派发行为遵循“多态点必须在协议中声明”

四、黄金口诀

“想让子类型可重写 → 方法必须声明在 protocol 本体;
协议扩展默认实现 = 可选默认行为,不能依赖它实现多态。”