6-30.【架构设计】架构设计中,什么是“延迟决策(Delay Decision)”?真实项目中是如何用它避免架构过早僵化的?

3 阅读2分钟

一、概念:延迟决策(Delay Decision)

延迟决策 = 在设计中尽量推迟对具体实现、技术选型或模块边界的硬性绑定,直到有足够信息支撑明确决策。

  • 目标:避免早期猜测导致架构僵化
  • 核心原则:面向抽象、保持可替换性
  • 本质是“先定义接口 / 协议 /抽象,后选择实现”,而不是一开始就固定实现方案

二、为什么重要

1️⃣ 避免早期耦合 / 反向依赖

  • 早期直接在模块内部使用具体实现 → 改动成本高
  • 延迟决策 → 用协议 /事件/抽象接口隔离依赖

2️⃣ 保持演进能力

  • 业务需求、技术栈、性能优化都会变化
  • 提前固定某种实现会阻碍迭代

3️⃣ 提升测试和重用性

  • 延迟绑定具体实现 → 可以在测试中 mock / stub
  • 可以在不同上下文复用模块

三、实践策略

1️⃣ 面向协议 / 接口编程

  • 逻辑层只依赖协议,不依赖具体实现
  • 具体实现延迟到 DI / 组装器 / Factory 注入
protocol AuthService {
    func login(username: String, password: String) async throws -> Bool
}

// 延迟到应用启动或测试时绑定具体实现
let authService: AuthService = NetworkAuthService()

2️⃣ 延迟注入 / 依赖反转

  • 构造注入 / 环境注入 / Factory 延迟决定依赖
struct LoginViewModel {
    let authService: AuthService
}
  • LoginViewModel 不关心使用的是 Network / Mock / Local Storage
  • 依赖决定可以推迟到 App 层

3️⃣ 使用 Effect / Publisher 封装副作用

  • 副作用(网络 / DB / 文件)封装在 Effect / Publisher
  • 模块内部只依赖抽象,具体执行逻辑可以后续替换
func loadUser() -> AnyPublisher<User, Error> { ... }
  • 未来可以换成 async/await、缓存策略、GraphQL,不破坏依赖模块

4️⃣ 延迟界面 / 业务细节

  • SwiftUI / MVVM:View 只依赖状态,具体渲染延迟到 View 层决定
  • Presenter / ViewModel 不写具体动画 /控件 → UI 变化不会影响逻辑层

四、真实项目应用案例

  1. 网络层
  • 早期不直接写 URLSession
  • 定义协议 NetworkClient → 延迟决定使用 URLSession / Alamofire / Mock
  1. Feature 模块
  • 不提前决定模块如何交互
  • 用事件 / Action / Publisher 定义接口 → 后期可换不同 UI / State 管理方案
  1. 数据库 / 持久化
  • 定义接口 UserRepository
  • 延迟决定 CoreData / Realm / SQLite / FileStorage

五、工程直觉

延迟决策策略典型实践
面向协议逻辑层只依赖抽象接口
延迟注入Factory / DI / Environment 注入具体实现
延迟副作用Effect / Publisher 封装异步任务
延迟 UI / 渲染View 只观察 State,不操作逻辑层

核心思想:先定义“能做什么”,再决定“怎么做”
这样架构在项目中期或后期仍能灵活演进,不会因为早期实现决定而僵化。