前端的 IOC 容器

随着前端工程越来越复杂化,简单的设计模式已经无法满足我们急剧扩张的功能需求。
在后端的开发中我们经常会听到IOC容器,而前端中却很少看到使用。学习 IOC 容器,其实本质上就是在学习依赖倒置这一设计原则。

依赖倒置

首先我们先来了解一下什么是依赖倒置,比方说我们要开发一辆车子,我们先设计轮子,然后设计底盘,然后设计车身,然后设计车子,
这时, 底盘依赖轮子,车身依赖底盘,车子依赖车身,我们看看伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class Wheel {
constructor() {
}
}
class Chassis {
constructor() {
this.wheel = new Wheel();
}
}
class Body {
constructor() {
this.chassis = new Chassis();
}
}
class Car {
constructor() {
this.body = new Body();
}
}

这时我们突然需要实例车子的时候能够修改车子轮子的大小,代码就会变成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

class Wheel {
constructor(size) {
this.size = size;
}
}
class Chassis {
constructor(size) {
this.wheel = new Wheel(size);
}
}
class Body {
constructor(size) {
this.chassis = new Chassis(size);
}
}
class Car {
constructor(size) {
this.body = new Body(size);
}
}

这样代码牵一发而动全身,对我来说代码的可维护性就非常差了

如何解决这个问题呢,我们反过来设计,先设计车子在设计车身,底盘,轮子。
这样依赖就倒转过来了,车身依赖与车子,底盘依赖与车身,轮子依赖与底盘,下面看看伪代码

这里我们通过依赖注入的方式来实现依赖倒置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

class Wheel {
constructor(size) {
this.size = size;
}
}
class Chassis {
constructor(instance:Wheel) {
this.wheel = instance;
}
}
class Body {
constructor(instance:Chassis) {
this.chassis = instance;
}
}
class Car {
constructor(instance:Body) {
this.body = instance;
}
}

这样当我们需要修改轮子的时候就不用再去修改底盘,车身,车子了,我们只需要单独修改轮子就可以了
但是这样还有一个问题,就是我们构建车子实例的时候回非常麻烦,我们需要先构建轮子,底盘,车身,才能构建好车子,实际开发中这样重复的工作量也是非常大的

1
2
3
4
const my_wheel = new Wheel(2);
const my_chassis = new Chassis(my_wheel);
const my_body = new Body(my_chassis);
const my_car = new Car(my_body);

IOC 容器就可以帮我们解决这样一个问题

IOC 通过配置,和注入的方式可以帮助我们完成上面这一个过程,我们只需要告诉 IOC 容器我们需要的类他就会根据配置自动帮我们完成依赖注入这一步。
我们大概可以像这样子去获取车子实例 const my_车子 = ioc_container.get(车子)

这里有个实现的非常好的js的ioc容器 InversifyJS

具体的用法可以参考下面的伪代码

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 第一步: 先申明接口和类型

interface IWheel {
private size:number;
}

interface IChassis {
private wheel:IWheel;
}

interface Ibody {
private chassis:IChassis;
}

interface ICar {
private body:IBody;
}

const TYPES = {
wheel:Symbol.for('wheel'),
chassis:Symbol.for('chassis'),
body:Symbol.for('body'),
car:Symbol.for('car'),
};

// 第二步:申明依赖关系
import { injectable, inject } from "inversify";
import "reflect-metadata";

@injectable()
class Wheel implements IWheel {
constructor(size) {
this.size = size;
}
}

@injectable()
class Chassis implements IChassis{
constructor(@inject(TYPES.wheel)instance:IWheel) {
this.wheel = instance;
}
}

@injectable()
class Body implements IBody{
constructor(@inject(TYPES.chassis)instance:IChassis) {
this.chassis = instance;
}
}

@injectable()
class Car implements Icar{
constructor(@inject(TYPES.body)instance:body) {
this.body = instance;
}
}
// 或者可以这样写
@injectable()
class Car implements ICar{
@inject(TYPES.body) private body: body;
}

// 第三步:创建容器的配置

import { Container } from "inversify";

const myContainer = new Container();
myContainer.bind<IWheel>(TYPES.wheel).to(Wheel);
myContainer.bind<IChassis>(TYPES.chassis).to(Chassis);
myContainer.bind<IBody>(TYPES.body).to(Body);
myContainer.bind<ICar>(TYPES.car).to(Car);

// 第四步:从容器获取实例
const car = myContainer.get<ICar>(TYPES.car);