Appearance
当然,这段代码是一个简单的依赖注入(Dependency Injection,简称 DI)实现示例。我们逐步解释每个部分的作用和功能。
1. injectable
装饰器
typescript
function injectable(target: any) {
// 将类标记为可注入
Reflect.defineMetadata('injectable', true, target);
}
- 作用:
injectable
装饰器用于标记一个类可以被依赖注入容器管理。 - 实现:通过
Reflect.defineMetadata
方法为目标类(target
)添加一个名为'injectable'
的元数据,并设置其值为true
。Reflect
是一个元数据操作库,需要额外安装reflect-metadata
包。
2. 标记 MyService
类为可注入
typescript
@injectable
class MyService {
// 服务逻辑
}
- 作用:使用
@injectable
装饰器标记MyService
类,使其可以被依赖注入容器管理。 - 实现:
@injectable
装饰器会将MyService
类标记为可注入,并在元数据中记录这个信息。
3. inject
装饰器
typescript
function inject(target: any, propertyKey: string, parameterIndex: number) {
const existingParameters = Reflect.getOwnMetadata('design:paramtypes', target, propertyKey) || [];
existingParameters[parameterIndex] = true; // 标记参数为可注入
Reflect.defineMetadata('design:paramtypes', existingParameters, target, propertyKey);
}
- 作用:
inject
装饰器用于标记构造函数参数需要依赖注入。 - 实现:
Reflect.getOwnMetadata
获取现有的参数元数据,如果没有则初始化为空数组。然后将指定参数位置(parameterIndex
)标记为true
。最后,通过Reflect.defineMetadata
方法将这些元数据重新设置到目标类上。
4. 使用 @inject
装饰器在 MyComponent
构造函数中注入 MyService
typescript
class MyComponent {
constructor(@inject private myService: MyService) {}
}
- 作用:使用
@inject
装饰器标记MyComponent
构造函数的参数myService
需要依赖注入。 - 实现:
@inject
装饰器会在参数元数据中记录这个依赖注入需求。
依赖注入流程
- 定义服务类:
MyService
被@injectable
装饰,表明它是一个可注入的服务。 - 定义使用依赖的类:
MyComponent
类的构造函数参数myService
被@inject
装饰,表明它需要注入一个MyService
实例。 - 注入依赖:在创建
MyComponent
实例时,依赖注入容器会读取元数据,发现myService
需要注入一个MyService
实例,并自动创建MyService
实例并注入。
依赖注入容器实现(示例)
为了使上述装饰器真正起作用,还需要一个简单的依赖注入容器实现:
typescript
import 'reflect-metadata';
class Container {
private static instances = new Map();
static get<T>(target: any): T {
if (this.instances.has(target)) {
return this.instances.get(target);
}
const injectable = Reflect.getMetadata('injectable', target);
if (!injectable) {
throw new Error(`Cannot inject ${target.name}. It is not marked as injectable.`);
}
const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
const params = paramTypes.map((param: any) => this.get(param));
const instance = new target(...params);
this.instances.set(target, instance);
return instance;
}
}
const myComponent = Container.get(MyComponent);
console.log(myComponent);
依赖注入容器工作原理
- 检查是否已有实例:如果容器中已经存在目标类的实例,直接返回该实例。
- 检查可注入性:通过元数据检查目标类是否标记为可注入。
- 获取构造函数参数类型:通过元数据获取构造函数参数类型,并递归获取这些参数的实例。
- 创建实例:使用获取的参数实例创建目标类实例,并存储到容器中。
- 返回实例:返回目标类的实例。
通过这种方式,可以实现简单的依赖注入,使得类之间的依赖关系更加清晰和易于管理。
依赖注入(Dependency Injection, DI)是一个设计模式,通过将对象的创建和对象之间的依赖关系管理从代码中剥离出来,转移到一个独立的依赖注入容器中,以提高代码的可维护性、可测试性和灵活性。装饰器在依赖注入模式中起到了标记和配置依赖关系的作用。以下是依赖注入及其配套装饰器解决的问题和优势:
1. 解耦
依赖注入将类的实例化和依赖管理移到外部容器,使得类不再直接依赖具体的实现,而是通过容器注入。这样做使得代码更加解耦,降低了模块之间的耦合度。
示例:
typescript
class UserService {
getUser() {
return 'User data';
}
}
class UserController {
constructor(private userService: UserService) {}
getUserData() {
return this.userService.getUser();
}
}
// 依赖注入方式
const userService = new UserService();
const userController = new UserController(userService);
2. 提高可测试性
通过依赖注入,可以方便地替换依赖项,从而进行单元测试。可以在测试中注入模拟对象或假对象,而不需要修改实际代码。
示例:
typescript
class MockUserService {
getUser() {
return 'Mock user data';
}
}
const mockUserService = new MockUserService();
const userController = new UserController(mockUserService);
console.log(userController.getUserData()); // 输出 'Mock user data'
3. 促进单一职责原则
依赖注入容器负责创建和管理对象的生命周期,类只需关注自身的职责,不需要负责依赖项的创建和管理。这符合单一职责原则,增强了代码的可维护性。
4. 方便扩展和维护
通过依赖注入,可以轻松地更换依赖项的实现而不影响使用这些依赖项的代码。例如,可以将 UserService
换成 AdvancedUserService
而不需要修改 UserController
。
示例:
typescript
class AdvancedUserService extends UserService {
getUser() {
return 'Advanced user data';
}
}
// 使用新的实现
const advancedUserService = new AdvancedUserService();
const userController = new UserController(advancedUserService);
console.log(userController.getUserData()); // 输出 'Advanced user data'
5. 集中管理配置
通过依赖注入容器,可以在一个地方集中管理所有依赖项的配置,便于统一管理和调整。
装饰器的作用
装饰器在依赖注入模式中起到标记和配置的作用。以下是装饰器在依赖注入中的具体作用:
- 标记类为可注入:通过
@injectable
装饰器标记某个类为可注入,这样依赖注入容器就能识别并管理它。
typescript
function injectable(target: any) {
Reflect.defineMetadata('injectable', true, target);
}
@injectable
class UserService {
// 服务逻辑
}
- 标记需要注入的依赖项:通过
@inject
装饰器标记构造函数参数为需要注入的依赖项,容器在实例化时会自动注入相应的依赖。
typescript
function inject(target: any, propertyKey: string, parameterIndex: number) {
const existingParameters = Reflect.getOwnMetadata('design:paramtypes', target, propertyKey) || [];
existingParameters[parameterIndex] = true;
Reflect.defineMetadata('design:paramtypes', existingParameters, target, propertyKey);
}
class UserController {
constructor(@inject private userService: UserService) {}
}
综合示例
typescript
import 'reflect-metadata';
// 装饰器定义
function injectable(target: any) {
Reflect.defineMetadata('injectable', true, target);
}
function inject(target: any, propertyKey: string, parameterIndex: number) {
const existingParameters = Reflect.getOwnMetadata('design:paramtypes', target, propertyKey) || [];
existingParameters[parameterIndex] = true;
Reflect.defineMetadata('design:paramtypes', existingParameters, target, propertyKey);
}
// 依赖注入容器
class Container {
private static instances = new Map();
static get<T>(target: any): T {
if (this.instances.has(target)) {
return this.instances.get(target);
}
const injectable = Reflect.getMetadata('injectable', target);
if (!injectable) {
throw new Error(`Cannot inject ${target.name}. It is not marked as injectable.`);
}
const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
const params = paramTypes.map((param: any) => this.get(param));
const instance = new target(...params);
this.instances.set(target, instance);
return instance;
}
}
// 使用装饰器标记类
@injectable
class UserService {
getUser() {
return 'User data';
}
}
@injectable
class UserController {
constructor(@inject private userService: UserService) {}
getUserData() {
return this.userService.getUser();
}
}
// 从容器中获取实例
const userController = Container.get(UserController);
console.log(userController.getUserData()); // 输出 'User data'
通过依赖注入和装饰器,代码变得更加模块化、易于测试和维护,同时增强了扩展性和配置的集中管理能力。这就是依赖注入及其装饰器的主要优势和解决的问题。