前言
nestjs是一个功能强大的node框架。其中有很多编程概念,例如:依赖注入(DI)系统、面向切面编程(AOP)支持。本文将带大家了解 IOC 与 DI 的概念。
IOC 与 DI
控制反转(英语:Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。 其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。
现在我们有一个简单的需求,我们有多个类,需要一个方法,可以获取某个类的实例,从而访问改类内部的方法。 比如,我们需要实现下面这段代码的:
class A {
b: B;
}
class B {
c: C;
}
class C {
hello() {
console.log('hello world');
}
}
const container = new Container();
const a = container.get(A);
// a.b.c.hello() === 'hello world'
第一步
我们在上面的例子里看到了 Container 类,首先我们先得知道 Container 需要做什么。
我们觉得它需要分析 A 这个类的属性中依赖了哪些东西,我们一眼就能看出 A 依赖了 B,而 B 依赖了 C,但是程序呢?
为了简化一下,这个地方需要有一个假设,假如我们的属性都是标准的类好了,现在就简单了。
class Container {
get(Module) {
// 创建对象
const obj = new Module();
// 拿到属性
const properties = Object.getOwnPropertyNames(obj);
for(let p of properties) {
if (!obj[p]) {
// 如果对象不存在,就往下创建
if (p === 'b') {
obj[p] = this.get(B);
} else if (p === 'c') {
obj[p] = this.get(C);
} else {}
}
}
}
}
我们使用递归的方式不断的get某一个类的实例,如果对象不存在,则不断的进行创建
第二步
现在我们只有3个类,核心功能很快就完成了,下一步我们来想想怎么优化 如果有另一个类,包裹了其他的几个实例怎么办呢。
class A {
b: B;
d: D;
}
class B {
c: C;
}
class D {
b: B;
c: C;
}
按照我们的核心代码,估计会创建出好几个B实例和C实例。
那么,开始优化吧,我们先加个缓存吧。
class Container {
cachae = {};
getName(Module) {
return Module.Name.toLowerCase();
}
get(Module) {
// 弄个缓存
if (this.cachae[this.getName(Module)]) {
return this.cachae[this.getName(Module)];
}
// 创建对象
const obj = new Module();
// 缓存起来下次用
this.cachae[this.getName(Module)] = obj;
// 拿到属性
const properties = Object.getOwnPropertyNames(obj);
for(let p of properties) {
if(!obj[p]) {
// 如果对象不存在,就往下创建
if(p === 'b') {
obj[p] = this.get(B);
} else if(p === 'c') {
obj[p] = this.get(C);
} else if(p === 'd') {
obj[p] = this.get(D);
} else {}
}
}
}
}
经过了缓存,我们可以保证在一次运行中,每个类都只会有一个对象实例存在了。
第三步
我们现在只能创建A、B、C、D、这四个类,是不是有点不太够。
没关系,我们这就教你支持任意的类。
聪明的人类想出了一个方法,如果我提前就知道了有哪些类,存在一个表里(Map)里, 下次用的时候如果名字一样就能匹配上了呢。
const globby = require('globby');
const path = require('path');
class Container {
cwd = process.cwd();
cache = {};
classTable = {};
init() {
// 这里使用了一个扫描机制,在执行初始化的时候把类的信息通通放到 classTable 下面,
// 之后我们就能直接使用啦,而且不用关心类名叫什么,把 26 个字母随意组合都可以
const fileResults = globby.sync(['**/**.ts', '**/**.js'], {
cwd: this.cwd,
ignore: [
'**/node_modules/**',
],
});
for (const name of fileResults) {
const exports = require(this.cwd + '/' + name);
// 把类名和类都存起来
this.classTable[this.getName(exports)] = exports;
}
}
getName(Module) {
return Module.name.toLowerCase();
}
get(Module) {
// 弄个缓存
if(this.cache[this.getName(Module)]) {
return this.cache[this.getName(Module)];
}
// 创建对象
const obj = new Module();
// 缓存起来下次用
this.cache[this.getName(Module)] = obj;
// 拿到属性
const properties = Object.getOwnPropertyNames(obj);
for(let p of properties) {
// 如果对象不存在,就往下创建
if(!obj[p]) {
// 如果表里有,就创建
if(this.classTable[p]) {
obj[p] = this.get(this.classTable[p]);
}
}
}
}
}