4-23.【协议导向编程】结合单元测试和依赖注入,说明协议如何降低耦合度。

5 阅读2分钟

一、核心原理

  1. 协议 = 能力契约

    • 模块 A 只依赖协议,而不依赖具体类型 B
    • 模块之间只约定“能做什么”,不关心“怎么做”
  2. 依赖注入(Dependency Injection, DI)

    • 在对象初始化或方法调用时,把依赖(协议类型)传入
    • 高层模块不自己创建具体类型 → 依赖反转(DIP)
  3. 单元测试

    • 因为高层只依赖协议 → 可以传入 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 → 高可控;
协议组合 → 松耦合。”