首页/开发/系统分析与架构设计速查/依赖注入与控制反转
架构风格与模式58 条

依赖注入与控制反转

依赖注入把依赖的创建与使用分离:对象声明所需依赖,由外部(容器或组合根)注入;它是实现依赖倒置原则的常用手段,利于测试与替换实现。

依赖注入是什么

依赖注入把依赖的创建与使用分离:对象只声明自己需要什么,由外部(容器或组合根)把实例传进来。它是实现依赖倒置原则的常用手段,让高层模块依赖抽象接口而不是具体实现,从而可以替换实现、便于单元测试。依赖注入不等于必须使用容器,小型项目在启动处手工装配往往更直观、启动更快、依赖关系也更清晰。

基本信息

注入方式构造器注入(推荐)、方法注入、属性注入
生命周期单例、作用域、瞬态三种,混用不当会引发状态串扰
组合根应用启动处集中装配依赖,其他位置不再 new 具体实现
常见容器Spring、.NET DI、Guice、Dagger,语言生态各有实现
与工厂区别工厂负责创建,注入负责传递,实际项目常结合使用

构造器注入示例

构造器注入示例
class OrderService {
  private final OrderRepository repo;   # 只依赖接口,不关心实现
  OrderService(OrderRepository repo) {  # 依赖由外部传入
    this.repo = repo;
  }
}
# 组合根:new OrderService(new MysqlOrderRepository(ds))
# 单元测试:new OrderService(new InMemoryOrderRepository())

生命周期选择

生命周期适用对象风险
单例无状态服务、配置、连接池持有请求级数据会串号
作用域一次请求内的上下文对象跨请求复用会读到旧数据
瞬态轻量、状态独立的对象频繁创建带来开销与内存压力

典型使用场景

  • 单元测试:注入内存实现,测试无需启动数据库与网络
  • 多环境适配:开发、测试、生产注入不同实现,代码不变
  • 替换第三方依赖:以接口隔离供应商 SDK,便于更换
  • 控制外部资源:把连接池、客户端等昂贵对象统一管理生命周期
  • 分层解耦:业务层只依赖抽象,持久化实现可自由替换
  • 条件装配:按配置决定注入哪个实现,避免散落的条件判断
判断注入是否用对的一个直观标准:业务类里是否还有 new 具体依赖的语句;如果还有,替换与测试都会立刻变难。
把容器当作全局服务定位器(到处从容器取依赖),依赖关系变得不可见,编译期无法发现缺失,测试也更难构造。

易错点

  • 大量使用属性注入或服务定位器,依赖关系隐藏在运行期,阅读代码看不出所需依赖
  • 生命周期配置错误(单例持有请求状态),出现用户数据串号这类严重问题
  • 容器内注册的依赖与实际使用不一致,启动才报错,缺少启动期校验
  • 为每个类都引入接口与容器注册,抽象层数远超实际变化点数量
  • 组合根之外仍存在 new 具体实现的代码,替换实现时漏改导致行为不一致

数据来源与参考

  • Martin Fowler bliki《Inversion of Control》
  • Robert C. Martin《Clean Architecture》
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌