白筱汐

想都是问题,做都是答案

0%

理解IOC容器(js描述)

前言

nestjs是一个功能强大的node框架。其中有很多编程概念,例如:依赖注入(DI)系统、面向切面编程(AOP)支持。本文将带大家了解 IOC 与 DI 的概念。

IOC 与 DI

控制反转(英语:Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。
其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。

现在我们有一个简单的需求,我们有多个类,需要一个方法,可以获取某个类的实例,从而访问改类内部的方法。
比如,我们需要实现下面这段代码的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,但是程序呢?

为了简化一下,这个地方需要有一个假设,假如我们的属性都是标准的类好了,现在就简单了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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个类,核心功能很快就完成了,下一步我们来想想怎么优化
如果有另一个类,包裹了其他的几个实例怎么办呢。

1
2
3
4
5
6
7
8
9
10
11
class A {
b: B;
d: D;
}
class B {
c: C;
}
class D {
b: B;
c: C;
}

按照我们的核心代码,估计会创建出好几个B实例和C实例。

那么,开始优化吧,我们先加个缓存吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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)里,
下次用的时候如果名字一样就能匹配上了呢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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]);
}
}
}
}
}