设计模式之依赖注入

控制反转(IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(DI)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

控制反转(IoC)依赖注入(DI)的关系:控制反转(IoC)是一种全新的设计模式。而依赖注入(DI)控制反转(IoC)的一种实现方式。

如果Class A中用到了Class B的对象实例b,一般情况下,需要在A的代码中显式的new一个B的对象。即:

1
2
3
4
5
6
7
8
9
10
11
class B {
constructor (name) {
this.name = name;
}
}

class A {
constructor () {
this.b = new B('b');
}
}

采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序(IoC容器)来将B对象在外部new出来并注入到A类里的引用中。而A要依赖B,必然要使用B的接口,那么:

  • 通过A的接口,把B传入
  • 通过A的构造,把B传入
  • 通过设置A的属性,把B传入

这个过程叫依赖注入(DI),分别对应着依赖注入(DI)的三种实现方式:

  • 接口注入
  • 构造注入
  • 属性注入

接口注入

接口注入是指类对外设置一个可访问的接口,通过该接口注入依赖。实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// class B
class B {
constructor (name) {
this.name = name;
}
}

// class A
class A {
constructor (name) {
this.name = 'A';
}

// 依赖注入接口
inject (name, dep) {
this[name] = dep;
}
}

var a = new A('a');

a.inject('b', new B('b'));

构造注入

构造注入是指在构造期间完成一个完整的、合法的对象。所有依赖关系在构造函数中集中呈现,依赖关系在构造时由容器一次性设定。即将依赖以构造函数参数的形式传进对象中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 依赖对象
class B {
constructor (name) {
this.name = name;
}
}

// class A依赖class B的实例b
class A {
constructor (name, dep) {
this.name = 'A';
this.b = dep; // 将依赖dep赋给b
}
}

var a = new A('a', new B('b'));

现在,class A可以通过构造函数传入一个依赖。

接下来,我们将优化class A的构造函数,使它可以传入任意的依赖对象:

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
// class B
class B {
constructor (name) {
this.name = name;
}
}

// class C
class C {
constructor (name) {
this.name = name;
}
}

// class A
class A {
constructor (name, deps = {}) {
this.name = 'A';
// 安装依赖
Object.keys(deps).map(k => {
this[k] = deps[k]
})
}
}

// class A的实例a依赖class B的实例b和class C的实例c
var a = new A('a', {
b: new B('b'),
c: new C('c')
})

属性注入

属性注入是指通过设置属性值的方式将依赖对象注入。通过设值方法设定依赖关系更加直观。如果依赖关系较为复杂,那么构造注入模式的构造函数也会相当庞大,而此时设值注入模式则更为简洁。

设值方法设定依赖实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// class B
class B {
constructor (name) {
this.name = name;
}
}

// class A
class A {
constructor (name) {
this.name = 'A';
}
}

var a = new A('a');

a.b = new B('b') // 设定依赖

总结

依赖注入解决的是一类问题,所以它是一种设计模式。即使你没有听说过它,但你可能已经用过它不止一次,只是没有意识到罢了。什么是依赖注入?依赖注入可以动态地为函数添加依赖。依赖注入在强类型语言中,如JAVA,比较常见,是一种解藕的方式。这并不是说在JavaScript中很少使用它。而是,在JavaScript中很容易就实现了这种动态依赖。JavaScript使用bind,apply,call等函数可以很方便地控制函数的参数和this变量,所以简单地依赖注入在很多情况下已经被不知不觉地使用。在AMD的模块定义中,其方式也是一种依赖注入。所以,在JavaScript中依赖注入可以说是无所不在的。