设计思路
- 依赖注入概念:依赖注入是一种设计模式,通过将依赖(外部服务)传递给需要它们的组件(方法),而不是让组件自己创建或查找依赖,从而实现解耦。
- TypeScript参数装饰器:用于在方法参数上添加元数据,在我们的场景中,可以用来标记哪些参数是外部服务依赖。
- 依赖管理容器:创建一个容器来存储和管理所有的外部服务实例,在运行时,根据参数装饰器标记,从容器中获取对应的依赖注入到方法中。
核心代码片段
// 依赖管理容器
const serviceContainer: { [key: string]: any } = {};
// 参数装饰器,用于标记依赖
function inject(serviceKey: string) {
return function (target: any, propertyKey: string, parameterIndex: number) {
if (!Reflect.hasMetadata('design:paramtypes', target, propertyKey)) {
Reflect.defineMetadata('design:paramtypes', [], target, propertyKey);
}
const existingTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey);
existingTypes[parameterIndex] = { serviceKey };
Reflect.defineMetadata('design:paramtypes', existingTypes, target, propertyKey);
};
}
// 模拟外部服务
class ExternalService1 {
public doSomething1() {
return 'Service1 doing something';
}
}
class ExternalService2 {
public doSomething2() {
return 'Service2 doing something';
}
}
// 注册服务到容器
serviceContainer['ExternalService1'] = new ExternalService1();
serviceContainer['ExternalService2'] = new ExternalService2();
// 使用依赖注入的类和方法
class MyClass {
public myMethod(@inject('ExternalService1') service1: ExternalService1, @inject('ExternalService2') service2: ExternalService2) {
return `${service1.doSomething1()} and ${service2.doSomething2()}`;
}
}
// 获取实例并调用方法
const myClassInstance = new MyClass();
const result = myClassInstance.myMethod();
console.log(result);
在项目维护和扩展方面的好处
- 维护方面:
- 代码可读性增强:通过参数装饰器和依赖注入,明确了方法对外部服务的依赖关系,使得代码阅读者能快速了解方法所需的外部资源。
- 易于修改:如果某个外部服务的实现发生变化,只需要在服务的具体实现类中修改,而不需要在每个依赖该服务的方法中修改,因为依赖是通过容器注入的,方法本身不关心具体的创建过程。
- 扩展方面:
- 添加新服务容易:要引入新的外部服务,只需要在容器中注册该服务,并在需要的方法参数上使用装饰器标记,不需要对现有代码结构进行大规模改动。
- 测试方便:在单元测试中,可以轻松地替换真实的外部服务为模拟服务,通过注入模拟对象,方便对方法进行独立测试,提高测试的准确性和可靠性。