6-27.【架构设计】依赖注入(DI)最容易被滥用的地方是什么?构造注入、属性注入、环境注入,各自的坑在哪里?

6 阅读2分钟

一、构造注入(Constructor / Init Injection)

class LoginViewModel {
    let authService: AuthenticationService
    
    init(authService: AuthenticationService) {
        self.authService = authService
    }
}

✅ 优点

  • 依赖明确:实例一创建就完整
  • 不可变性:依赖不能被随意修改 → 模块边界清晰
  • 易测试:可以直接传入 Mock

⚠️ 滥用/坑

  1. 依赖膨胀
  • 一个对象的依赖越来越多 → 构造函数臃肿
  • Example: ViewModel(auth:auth, db:db, logger:logger, network:network...)
  1. 高层对象管理复杂
  • 大量嵌套依赖 → 初始化链条变长
  • 需要依赖容器/组装器,否则会出现“依赖爆炸”
  1. 适合静态依赖,不适合可选或延迟依赖
  • 如果依赖创建昂贵或可选,构造注入会迫使提前创建

二、属性注入(Property Injection / Setter Injection)

class LoginViewModel {
    var authService: AuthenticationService!  // 后注入
}

✅ 优点

  • 延迟依赖 / 可选依赖灵活
  • 不用在构造函数里传所有东西

⚠️ 滥用/坑

  1. 不安全 / 可变依赖
  • 对象可以在运行时改变依赖 → 状态不可预测
  • 很容易出现 “nil crash” 或逻辑异常
  1. 测试不确定性
  • Mock 必须在使用前注入
  • 使用顺序错误 → 难复现 bug
  1. 破坏模块边界
  • 外部可以随意改依赖 → 内部假定的约束被破坏

总结:属性注入易用,但破坏单向依赖,容易导致“模块边界失效 + 可测试性下降”


三、环境注入(Environment / Context Injection)

SwiftUI / TCA 常用:

struct ContentView: View {
    @Environment(.authService) var authService
}

✅ 优点

  • 全局可访问,减少传递链
  • 适合 跨多层 UI 或公共配置
  • 易替换 / Mock

⚠️ 滥用/坑

  1. 隐藏依赖
  • 从 View 或对象里直接读取依赖 → 构造函数里不体现依赖
  • “模块依赖不明确” → 破坏边界
  1. 不可追踪 / 可测试性差
  • Test / Preview 时必须提供环境
  • 默认环境值可能覆盖或不一致 → 测试难复现
  1. 频繁变更导致不可预测行为
  • Environment 改变 → 下层 View 自动重绘
  • 高频变化容易引发性能问题 / 状态混乱

四、总结:滥用模式 & 安全策略

注入方式容易滥用的地方建议策略
构造注入依赖过多 → 构造函数臃肿保持核心依赖使用构造注入,复杂依赖用组装器或容器
属性注入运行时可变 / nil / 顺序错误尽量避免跨模块使用,最好局部测试 / 延迟创建
环境注入隐藏依赖、跨层访问、测试复杂用于跨层共享配置/全局依赖,不用于核心业务逻辑,确保提供默认值和测试覆盖

核心原则:

  1. 依赖越核心越明确 → 构造注入
  2. 延迟 / 可选依赖 → 属性注入谨慎使用
  3. 全局 / 跨层共享依赖 → Environment / Context 注入