一、构造注入(Constructor / Init Injection)
class LoginViewModel {
let authService: AuthenticationService
init(authService: AuthenticationService) {
self.authService = authService
}
}
✅ 优点
- 依赖明确:实例一创建就完整
- 不可变性:依赖不能被随意修改 → 模块边界清晰
- 易测试:可以直接传入 Mock
⚠️ 滥用/坑
- 依赖膨胀
- 一个对象的依赖越来越多 → 构造函数臃肿
- Example:
ViewModel(auth:auth, db:db, logger:logger, network:network...)
- 高层对象管理复杂
- 大量嵌套依赖 → 初始化链条变长
- 需要依赖容器/组装器,否则会出现“依赖爆炸”
- 适合静态依赖,不适合可选或延迟依赖
- 如果依赖创建昂贵或可选,构造注入会迫使提前创建
二、属性注入(Property Injection / Setter Injection)
class LoginViewModel {
var authService: AuthenticationService! // 后注入
}
✅ 优点
- 延迟依赖 / 可选依赖灵活
- 不用在构造函数里传所有东西
⚠️ 滥用/坑
- 不安全 / 可变依赖
- 对象可以在运行时改变依赖 → 状态不可预测
- 很容易出现 “nil crash” 或逻辑异常
- 测试不确定性
- Mock 必须在使用前注入
- 使用顺序错误 → 难复现 bug
- 破坏模块边界
- 外部可以随意改依赖 → 内部假定的约束被破坏
总结:属性注入易用,但破坏单向依赖,容易导致“模块边界失效 + 可测试性下降”
三、环境注入(Environment / Context Injection)
SwiftUI / TCA 常用:
struct ContentView: View {
@Environment(.authService) var authService
}
✅ 优点
- 全局可访问,减少传递链
- 适合 跨多层 UI 或公共配置
- 易替换 / Mock
⚠️ 滥用/坑
- 隐藏依赖
- 从 View 或对象里直接读取依赖 → 构造函数里不体现依赖
- “模块依赖不明确” → 破坏边界
- 不可追踪 / 可测试性差
- Test / Preview 时必须提供环境
- 默认环境值可能覆盖或不一致 → 测试难复现
- 频繁变更导致不可预测行为
- Environment 改变 → 下层 View 自动重绘
- 高频变化容易引发性能问题 / 状态混乱
四、总结:滥用模式 & 安全策略
| 注入方式 | 容易滥用的地方 | 建议策略 |
|---|---|---|
| 构造注入 | 依赖过多 → 构造函数臃肿 | 保持核心依赖使用构造注入,复杂依赖用组装器或容器 |
| 属性注入 | 运行时可变 / nil / 顺序错误 | 尽量避免跨模块使用,最好局部测试 / 延迟创建 |
| 环境注入 | 隐藏依赖、跨层访问、测试复杂 | 用于跨层共享配置/全局依赖,不用于核心业务逻辑,确保提供默认值和测试覆盖 |
核心原则:
- 依赖越核心越明确 → 构造注入
- 延迟 / 可选依赖 → 属性注入谨慎使用
- 全局 / 跨层共享依赖 → Environment / Context 注入