一、核心原理
-
协议 = 能力契约
- 模块 A 只依赖协议,而不依赖具体类型 B
- 模块之间只约定“能做什么”,不关心“怎么做”
-
依赖注入(Dependency Injection, DI)
- 在对象初始化或方法调用时,把依赖(协议类型)传入
- 高层模块不自己创建具体类型 → 依赖反转(DIP)
-
单元测试
- 因为高层只依赖协议 → 可以传入 mock 或 stub 测试替身
- 避免对真实依赖的耦合
✅ 结论:协议 + DI + 单元测试 → 降低耦合、增强可替换性
二、示例:用户服务依赖数据库
1️⃣ 定义协议(抽象依赖)
protocol UserDatabase {
func fetchUser(id: Int) -> String
}
2️⃣ 实现具体类型
class RealDatabase: UserDatabase {
func fetchUser(id: Int) -> String {
// 真实数据库查询
return "User(id) from DB"
}
}
3️⃣ 服务依赖协议(高层不关心实现)
class UserService {
let db: UserDatabase // 协议依赖
init(db: UserDatabase) {
self.db = db
}
func getUserName(id: Int) -> String {
db.fetchUser(id: id)
}
}
三、单元测试:注入 Mock
struct MockDatabase: UserDatabase {
func fetchUser(id: Int) -> String {
return "MockUser(id)"
}
}
let mockDB = MockDatabase()
let service = UserService(db: mockDB)
print(service.getUserName(id: 1)) // 输出: MockUser1 ✅
特点:
- UserService 完全不依赖具体数据库
- 测试可预测且无需真实数据库
- 依赖可随时替换 → 解耦
四、工程实践建议
| 做法 | 作用 |
|---|---|
| 协议定义依赖 | 高层只依赖能力,不依赖具体类型 |
| 构造器/方法注入依赖 | 支持依赖注入,易替换 |
| protocol extension 提供默认实现 | 可复用行为,但类型可覆盖,保持灵活性 |
| 单元测试使用 mock/stub | 测试高效、可预测 |
| 组合多个协议 | 模块可组合能力,不用继承 → 降低耦合 |
五、设计口诀
“模块依赖协议 → 只关心能力;
构造器注入依赖 → 易替换;
测试传入 mock → 高可控;
协议组合 → 松耦合。”