# 定义
依赖注入(Dependency Injection, DI)是一种设计模式,用来管理对象的依赖关系。核心思想是将对象所依赖的组件交由外部注入,而不是让对象自己创建或管理它们的依赖。这样做的好处是可以减少代码的耦合性,提高代码的可维护性、测试性和扩展性。
依赖注入其实 没有跳过或者使用其他方法代替【生成实例】这个过程,而是采用某个机制(如 IoC 容器)来管理依赖的实例化和分发,改变了传统的由代码显式创建实例的行为。这种机制核心是 ** 控制反转(Inversion of Control, IoC):** 组件不再主动创建依赖,而是交由容器来提供。更多内容可查看第五点:详解。
# 核心概念
- 依赖:一个类中依赖的其他类或组件,通常是服务、库等功能模块。
- 注入:把依赖关系从外部传入到当前类中,而不是在类内部直接创建依赖对象。
# 三种方式
- 构造函数注入:通过构造函数传递依赖对象。
- 方法注入:通过方法参数传递依赖对象。
- 属性注入:通过类的属性传递依赖对象。
# 优点
- 解耦:类不再需要直接管理它的依赖对象,依赖的创建与管理交给外部系统来处理。
- 可测试性:可以轻松地替换依赖为测试版本(例如 Mock 对象),使得单元测试更容易。
- 可维护性和扩展性:当需要更换某个依赖实现时,无需修改依赖使用的代码。
# 简单示例
- 构造函数注入
class Engine { | |
start() { | |
console.log("Engine started"); | |
} | |
} | |
class Car { | |
constructor(engine) { | |
this.engine = engine; | |
} | |
startCar() { | |
this.engine.start(); | |
} | |
} | |
// 外部创建依赖并注入 | |
const engine = new Engine(); | |
const car = new Car(engine); | |
car.startCar(); |
- 方法注入
class Car { | |
startCar(engine) { | |
engine.start(); | |
} | |
} | |
// 使用时将依赖通过方法传入 | |
const engine = new Engine(); | |
const car = new Car(); | |
car.startCar(engine); |
- 属性注入
class Car { | |
setEngine(engine) { | |
this.engine = engine; | |
} | |
startCar() { | |
this.engine.start(); | |
} | |
} | |
// 使用时将依赖通过属性传入 | |
const engine = new Engine(); | |
const car = new Car(); | |
car.setEngine(engine); | |
car.startCar(); |
# 详解
# 容器生成实例的方式
- 单例:默认情况下,依赖通常以单例的形式提供。容器会在首次请求时创建实例,并缓存起来。之后的所有请求都返回相同的实例。
这种模式下,如果在不同文件中注入了相同依赖,一方更新,另一方也会导致更新
class Logger {} | |
Container.register(Logger); // 单例注册 |
- 多例:如果需要每次都创建新的实例,可以通过工厂函数或特定的配置。
Container.register(Logger, { | |
useFactory: () => new Logger(), | |
}); // 每次都会调用工厂函数生成实例 |
# 和直接 new 生成实例的区别
依赖注入的主要价值并不在于生成实例本身,而在于以下几点:
- 解耦
通过 DI,组件只依赖接口(契约)而非具体实现。例如:
class Service { | |
constructor(private logger: ILogger) {} | |
} |
组件 Service 并不知道 ILogger 的具体实现。容器可以动态地注入不同的实现(如 ConsoleLogger 或 FileLogger)。
- 可测试性
DI 容易让依赖的实例被替换,例如在测试环境中注入模拟对象(Mock)。
Container.register(Logger, { | |
useValue: new MockLogger(), | |
}); |
- 生命周期管理
容器可以统一管理依赖的生命周期,比如创建、销毁等,避免手动管理实例的复杂性。
- 灵活性
通过容器可以灵活地改变依赖。例如,可以根据配置动态注入不同实现,而无需改动组件代码。