4-29.【协议导向编程】POP 如何帮助大型工程实现高内聚、低耦合,同时提升测试覆盖率?

0 阅读2分钟

一、核心原理

  1. 协议 = 能力契约

    • 抽象行为而非具体类型
    • 模块只依赖协议 → 低耦合
  2. 协议扩展 = 默认实现 / 模板方法

    • 提供通用逻辑
    • conforming 类型可覆盖 → 保持灵活性
  3. 类型组合 + 值类型(struct)

    • struct conform 多个协议 → 高内聚
    • 每个类型只关心自身能力
  4. 依赖注入 + 泛型约束

    • 高层模块通过协议注入依赖 → 易替换 mock → 提升测试覆盖率

二、设计策略

1️⃣ 小协议、单一职责 → 高内聚

protocol Fetchable {
    func fetch(id: Int) -> String
}

protocol Savable {
    func save(data: String)
}
  • 每个协议只关心一个功能
  • 类型 conform 一个或多个协议组合能力 → 高内聚

2️⃣ 模块只依赖协议 → 低耦合

class UserService {
    let fetcher: Fetchable
    let saver: Savable
    
    init(fetcher: Fetchable, saver: Savable) {
        self.fetcher = fetcher
        self.saver = saver
    }
    
    func updateUser(id: Int, newName: String) {
        var data = fetcher.fetch(id: id)
        data = newName
        saver.save(data: data)
    }
}
  • UserService 不依赖具体类型
  • Fetchable / Savable 可以在不同模块实现 → 模块间低耦合

3️⃣ 默认实现 → 可复用逻辑

extension Savable {
    func save(data: String) {
        print("Default save: (data)")
    }
}
  • 所有 conforming 类型可复用模板方法
  • 高内聚逻辑集中 → 不重复实现

4️⃣ 依赖注入 + Mock → 高测试覆盖率

struct MockFetcher: Fetchable {
    func fetch(id: Int) -> String { "MockUser" }
}

struct MockSaver: Savable {
    func save(data: String) { print("Mock saved (data)") }
}

let service = UserService(fetcher: MockFetcher(), saver: MockSaver())
service.updateUser(id: 1, newName: "Alice")
// Mock saved Alice
  • 测试不依赖真实数据库
  • 每个模块都可单独测试 → 高覆盖率
  • 类型替换灵活 → 易扩展

三、工程实践策略

策略作用
小协议、单一职责高内聚,类型只关心自身能力
模块间依赖协议低耦合,替换实现不影响其他模块
协议扩展默认实现提供模板方法,复用逻辑
泛型约束协议核心逻辑静态派发 → 高性能
依赖注入高层模块可替换依赖 → 易测试
Mock / Stub提升单元测试覆盖率,保证模块独立测试

四、POP 带来的整体优势

  1. 高内聚

    • struct / class 只 conform 所需协议
    • 功能集中在能力模块 → 易理解、易维护
  2. 低耦合

    • 模块之间通过协议交互
    • 改动模块内部实现不会影响其他模块
  3. 高测试覆盖率

    • 协议 + DI → 可注入 mock / stub
    • 单元测试独立、可控
    • 无需依赖真实服务或数据库
  4. 可复用与可扩展

    • 新模块或功能直接 conform已有协议
    • 默认实现 + 泛型约束 → 核心逻辑复用

五、设计口诀

“协议抽象能力 → 模块依赖协议 → 小协议高内聚 → 默认实现复用逻辑 → DI + mock 提升测试覆盖率。”