Dependency Injection 这个概念是我之前在实习的时候做 Java 开发的时候接触的。Dependency Injection 可以大大降低模块之间的耦合度,提高系统的可扩展性和鲁棒性,不过这个概念对于新人来说理解起来还是存在比较大的障碍。由于当时实习的时间比较短,对于这个概念我并没有吃透。这次学习 Node.js 的时候,又在 awilix 这个库里面遇到了这个概念。以此为契机就来好好学习一些 Dependency Injection 和其后的设计逻辑与方法。
下面的内容翻译自:Dependency Injection in Node.js。这篇文章浅显地介绍了 Dependency Injection 的基本理念。选择这篇文章是因为我在阅读 awilix 模块作者关于 Dependency Injection 的系列文章中时,作者在开篇提议阅读此文。
不过这篇文章毕竟是 2015 年的文章,在 js 的一些语法和模块细节上和今时今日的有些不同,但是并不妨碍我们对于其核心理念的理解。
1 使用 Dependency Injection 的理由
1.1 解耦 (Decoupling)
Dependency Injection 使你的模块耦合度降低,从而提升代码的可维护性。
1.2 更简单的单元测试
比起需要硬编码的依赖关系,你可以将依赖关系传输进入你要用的模块。在大多数场合下使用这种范式你不必要使用 proxyquire 这样的模块。
这一段作者写的比较含糊。其实意思是在使用 Dependency Injection 场景下,我们在独立测试一些单元功能的时候,对于其他模块可以通过注入 Mock 对象,从而将待测试的模块独立出来进行测试。
1.3 更快速的开发
在使用了 Dependency Injection 的场景下,在接口定义好了以后,开发会更加容易,Merge conflict 会更少。
2 如何在 Node.js 中使用 Dependency Injection
下面我们来看看如何在不适用 Dependency Injection 的前提下开发应用,然后看看如何进行转化。
2.1 不使用 Dependency Injection 的例子
下面是一段简单的没有使用 Dependency Injection 的代码:
1 | // team.js |
对应的测试可能是:
1 | // team.spec.js |
在上面的代码中我们做的是创建了一个名为team.js
的模块,该模块可以返回属于一个 team 的用户列表。为了实现这一功能,我们导入User
模块,然后我们再调用其find
方法返回用户列表。
看起来不错,是吗?但是当我们需要进行测试时,我们必须要使用sinon的 test stubs.
在测试文件中,我们需要引入 User 模块,为其 stub 一个find
方法。注意,我们在这里要使用 sandbox 功能,这样我们不需在测试完成后回复find
的原函数。
注意:如果原始对象使用了
Object.freeze
,那么 stubs 将不会起作用。
2.2 使用 Dependency Injection 的例子
1 | // team.js |
你可以使用下面的这个文件来进行测试
1 | // team.spec.js |
那么,使用了 Dependency Injection 的版本同之前的版本有什么区别呢?首先你可能注意到的是这里使用了工厂模式:我们使用这种设计模式来将 options/dependencies inject 到新创建的对象中 - 这里是我们注入User
模块的方法。
在测试文件中我们还需要创建一个 fake model 来代表User
模块,然后将这个伪造的模块传递给工厂函数。很简单,不是吗?
3 Dependency Injection in Real Projects
你可以在非常多的开源项目中发现 Dependency Injection 的例子。例如,你在日常工作中常常用到的 Express/Koa 的大部分中间件都使用了这种技术。
3.1 Express Middlewares
1 | var express = require('express'); |
上面的代码片段使用了基于工厂模式的 Dependency Injection:对应 session 中间件我们传递了一个connect-session-knex
模块。这个模块需要实现session
模块调用需要的借口。
在这个例子中,connect-session-knex
模块需要实现下面的方法:
store.destroy(sid, callback)
store.get(sid, callback)
store.set(sid, session, callback)
3.2 Hapi plugins
Dependency Injection 的概念还可以在 Hapi 中找到。下面的例子中,handlebars
模块被作为 view engine 注入给 Hapi 使用:
1 | server.views({ |