天天看点

依赖注入 in 前端 && Typescript 实现依赖注入

最近因为工作需要,研究了vscode的代码,看文档时发现其中提到了Dependency Injection,并且在类的构造函数中看到了这样的写法。

因为对此依赖注入比较陌生,特地去了解了一番,故有此文章。

注意:此文章从前端角度说明依赖注入,非前端同学可以绕道

总结的来说,依赖注入是一种设计模式,因为它解决的是一类问题,这类问题是与依赖相关的。

要知道依赖注入是解决什么问题,我们需要先了解一个原则:依赖倒转原则。

这是设计模式的六大原则之一,其核心是面向接口编程。

它要求我们组织代码时:

高层模块不应该依赖低层模块。两个都应该依赖抽象

抽象不应该依赖细节

细节应该依赖抽象

意思是我们编程时,对系统进行模块化,两个模块之间有依赖,例如模块A依赖模块B,那么根据依赖倒转原则,我们开发时,模块A应该依赖模块B的接口,而不是依赖模块B的实现。

下图描述了此原则提倡的关系:

依赖注入 in 前端 && Typescript 实现依赖注入
注意:虽然模块A只依赖接口编程,但在运行的时候,它还是需要有一个具体的模块来负责模块A需要的功能的,所以模块A在【运行时】是需要一个【真的】模块B,而不是它的接口。

所以上图中,Module和Interface之间的线是包含,而不是关联。

对前端来说,一般较少有抽象,如果不是使用Typescript的话,就更少接触接口了。

但是,依赖注入却是一直都存在,只是许多同学没有认出来而已。

看下面这个栗子,是前端普遍存在的依赖注入

简单来说,依赖注入就做两件事情

初始化被依赖的模块

注入到依赖模块中

再看一次栗子:

一般来说,在开发时,我们需要一个对象的能力,一般是自己去创建一个。

那如果,我需要很多个对象,而我需要的对象又依赖了其他对象,这时我是不是得对每个对象都创建一遍。

假设对象 A 依赖 B , C , D, 同时 B 又依赖 E。

如图:

依赖注入 in 前端 && Typescript 实现依赖注入

创建一个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>

继续阅读