控制反转
按照维基百科,IoC(Inversion of Control)控制反转,是面向对象编程中的一种设计原则,用来降低计算机代码之间的耦合度。先来了解一下控制反转可以帮助我们解决什么问题。假如现在有两个类 A 和 B。依赖关系如下:
graph LR
A-->B
代码如下:
class A {
private b: B;
constructor() {
this.b = new B();
}
}
class B {
constructor() {}
}
假如现在需要对 B 进行修改,在 constructor 中增加参数,那么需要做的修改如下:
class A {
private b: B;
constructor(name: string) {
// 实例化需要增加参数 name
this.b = new B(name);
}
}
class B {
private name: string;
// 增加参数 Name
constructor(name: string) {
this.name = name;
}
}
可以看到对 B 的修改会影响到使用它的 A ,如果所有的依赖都是按照这种方式构建的,那么当类的数量多起来,每一次修改都需要修改多个地方,维护难度会非常大。
那么如何可以解决这个问题呢?最简单的做法是把 B 实例化以后作为 A constructor 的参数传入即可。
代码变更为:
class A {
private b: B;
// 直接传入 b 的实例
constructor(b: B) {
this.b = b;
}
}
class B {
private name: string;
// 增加参数 Name
constructor(name: string) {
this.name = name;
}
}
这就是控制反转带来的好处,以后 A 就不需要关心 B 的实现细节了。但是这样好像还不够好,哪一天我需要修改 A 时候是不是也要把实例化 A 的代码都修改一遍。那有没有一种办法可以把 B 实例直接赋值到定义的属性上呢?通过 TS 修饰器实现的依赖注入是可以的。
依赖注入
需要使用到的库
npm i reflect-metadata --save
在项目中引入依赖
// index.ts
import "reflect-metadata";
基本结构图
graph LR
Service[Service]--往 Container 注册类-->Container[Container 保存类和实例]
Container[Container 保存类和实例]--从 Container 获取注册的类-->Inject[Inject]
代码实现
// index.ts
import "reflect-metadata";
class Container {
private ContainerMap = new Map<string | symbol, any>();
public set = (id: string | symbol, value: any): void => {
this.ContainerMap.set(id, value);
};
public get = <T extends any>(id: string | symbol): T => {
return this.ContainerMap.get(id) as T;
};
public has = (id: string | symbol): Boolean => {
return this.ContainerMap.has(id);
};
}
const ContainerInstance = new Container();
interface ConstructableFunction extends Function {
new (...args): any;
}
export function Service(config?: {
id?: string;
// 默认以单例的方式保存
singleton?: boolean;
}): Function {
return (target: ConstructableFunction) => {
const { id, singleton = true } = config || {};
let singleInstance;
// 没有 id 生成一个 symbol 的 id
const serviceId = id || Symbol(target.name);
if (typeof serviceId === "string" && ContainerInstance.has(serviceId)) {
throw new Error(`${serviceId} has been used`);
}
Reflect.defineMetadata("custom:id", serviceId, target);
// 获取 construct 的参数类型
const args = Reflect.getMetadata("design:paramtypes", target) as any[];
if (singleton) {
// 获取对应的参数实例传入
const instances =
args?.map((arg) => {
const id = Reflect.getMetadata("custom:id", arg);
const instance = ContainerInstance.get(id as any);
return instance;
}) || [];
singleInstance = new target(...instances);
}
ContainerInstance.set(serviceId, singleInstance || target);
};
}
// 使用 id 定义模块后,需要使用 id 来注入模块
export function Inject(id?: any): PropertyDecorator {
return (target: Object, propertyKey: string | symbol) => {
const Dependency = Reflect.getMetadata("design:type", target, propertyKey);
const serviceId = id || Reflect.getMetadata("custom:id", Dependency as any);
const dependency = ContainerInstance.get(serviceId as any);
// 给属性注入依赖
Object.defineProperty(target, propertyKey, {
value: dependency,
});
};
}
@Service()
class A {
public main(...args: any[]): void {
console.info("I am A", ...args);
}
}
@Service()
class B {
public main(...args: any[]): void {
console.info("I am B", ...args);
}
}
@Service()
class CustomerController {
// 使用 Inject 注入
@Inject()
private a!: A;
// constructor 的参数自动注入
constructor(private b: B) {
this.b.main(" bbb ");
this.main();
}
public main(): void {
this.a.main(" aaa ");
}
}
存在的问题
- @Service 的注册需要依次注册,不然使用会报错。(可以先收集依赖,根据依赖树来决定实例化顺序)
- 不支持依赖的懒加载