6-29.【架构设计】如果一个模块被 10 个模块依赖,如何设计它的 API?

3 阅读2分钟

一、设计原则

  1. 最小接口原则(Interface Segregation)

    • 提供尽量精简、单一职责的 API
    • 不暴露内部实现细节
    • 依赖方只看到它需要的功能
  2. 稳定性优先

    • 依赖方越多,API 越难改动
    • 尽量保持向后兼容,避免破坏依赖链
  3. 抽象化和协议化

    • 依赖模块面向协议而非具体实现
    • 保持可替换性和测试能力
  4. 单向依赖

    • 模块不依赖使用它的模块
    • API 不泄露依赖方上下文

二、API 设计策略

1️⃣ 封装核心能力,隐藏实现

  • 只暴露必需方法 / 类型
  • 内部实现细节用 internal / private / target-specific hidden
public protocol AuthService {
    func login(username: String, password: String) async throws
    func logout() async
}
  • 使用模块的 10 个模块只看到 AuthService 协议
  • 内部实现可随意优化

2️⃣ 分层 API,提供组合入口

  • 如果模块功能复杂,拆分成 子协议 / 小接口
  • 避免单个接口过大,依赖方只引入需要的部分
public protocol UserRepository {
    func fetchUser(id: String) async -> User
}
public protocol UserUpdater {
    func updateUser(_ user: User) async
}
  • 不同依赖方只依赖 UserRepositoryUserUpdater

3️⃣ 保持不可变 / 纯函数接口优先

  • 避免依赖方修改内部状态
  • 提供返回新实例或异步任务,而不是可变共享对象
func filteredUsers(role: Role) -> [User]
  • 保持线程安全、可预测性

4️⃣ 异步 / 副作用通过 Effect / Publisher 暴露

  • 避免模块直接操作依赖方 UI / 状态
  • 使用 Combine / async/await / callback 返回结果
func observeLoginState() -> AnyPublisher<LoginState, Never>
  • 模块只负责业务逻辑
  • 依赖方根据状态做 UI / 逻辑处理

5️⃣ 版本管理与向后兼容

  • 被多模块依赖 → API 升级必须小心

  • 建议:

    • 使用默认参数 / protocol extension 保持旧 API
    • 通过 deprecate 标记引导迁移

三、工程实践建议

方向方法目的
可替换性面向协议 / 抽象接口依赖方可以替换实现,测试容易
隐藏内部internal / fileprivate / private避免依赖方误用内部实现
可扩展小接口 + 默认实现方便未来增加功能,不破坏旧依赖
状态安全返回值 / Publisher / async避免共享可变状态
版本安全deprecate / default arg避免 10 个模块同时改动 → 崩盘

四、核心结论

API 设计的目标 = 最小、稳定、抽象、可预测

物理上被 10 个模块依赖,不是“多点调用”的问题,而是 每个依赖方只看到它需要的协议和能力,不暴露内部状态和实现