最近因为工作需要,研究了vscode的代码,看文档时发现其中提到了Dependency Injection,并且在类的构造函数中看到了这样的写法。
因为对此依赖注入比较陌生,特地去了解了一番,故有此文章。
注意:此文章从前端角度说明依赖注入,非前端同学可以绕道
总结的来说,依赖注入是一种设计模式,因为它解决的是一类问题,这类问题是与依赖相关的。
要知道依赖注入是解决什么问题,我们需要先了解一个原则:依赖倒转原则。
这是设计模式的六大原则之一,其核心是面向接口编程。
它要求我们组织代码时:
高层模块不应该依赖低层模块。两个都应该依赖抽象
抽象不应该依赖细节
细节应该依赖抽象
意思是我们编程时,对系统进行模块化,两个模块之间有依赖,例如模块A依赖模块B,那么根据依赖倒转原则,我们开发时,模块A应该依赖模块B的接口,而不是依赖模块B的实现。
下图描述了此原则提倡的关系:

注意:虽然模块A只依赖接口编程,但在运行的时候,它还是需要有一个具体的模块来负责模块A需要的功能的,所以模块A在【运行时】是需要一个【真的】模块B,而不是它的接口。
所以上图中,Module和Interface之间的线是包含,而不是关联。
对前端来说,一般较少有抽象,如果不是使用Typescript的话,就更少接触接口了。
但是,依赖注入却是一直都存在,只是许多同学没有认出来而已。
看下面这个栗子,是前端普遍存在的依赖注入
简单来说,依赖注入就做两件事情
初始化被依赖的模块
注入到依赖模块中
再看一次栗子:
一般来说,在开发时,我们需要一个对象的能力,一般是自己去创建一个。
那如果,我需要很多个对象,而我需要的对象又依赖了其他对象,这时我是不是得对每个对象都创建一遍。
假设对象 A 依赖 B , C , D, 同时 B 又依赖 E。
如图:
创建一个a的实例:
可以看到,为了创建类A的一个实例,我们需要先创建 B,C,D 的实例,而为了创建一个B的实例,我们又需要创建一个E的实例。如果依赖的e模块做了改变,构造时需要多传入一个参数,那么我们的代码也得跟着改变,随着项目发展,工程越来越大,代码将会变得非常不好维护。
这是我们很会自然想到,有没方法使依赖模块与被依赖模块的初始化信息解耦。
其实,js实现依赖注入的方式有多种,简单列举一些:
基于Injector、Cache和函数参数名的依赖注入
AngularJS中基于双Injector的依赖注入
inversify.js——Javascript技术栈中的IoC容器
TypeScript中基于装饰器和反射的依赖注入
......
那现在我用在工作中使用的模式来讲,也是基于Typescript的装饰器,但不需要用到反射。
先看下如果使用了依赖注入后,模块A是如何使用其他模块的。
是不是很简单明了,以后使用A模块的能力时,只需要实例化A(实例化时需要使用封装的方法),就不需要管他依赖的参数了。
那么这是如何实现的呢,下面来看下具体方式。
首先,每个类都需要依照自己的接口来实现,接口需要传入createDecorator,此方法会返回一个装饰器,以B模块为例:
依赖注入(Dependency Injection),是这样一个过程:由于某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,所以客户类只定义一个注入点。在程序运行过程中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,然后将其注入到客户类中,保证客户类的正常运行。
参考文章:
<a href="http://www.cnblogs.com/leoo2sk/archive/2009/06/17/1504693.html">依赖注入那些事儿</a>
<a href="http://imweb.io/topic/571b567505637d4c67ae3f64">前端需要知道的 依赖注入(Dependency Injection, DI)</a>