如何在Nodejs中进行有效的dependency injection?

我们从一些参考代码开始

var express = require('express'); var app = express(); var session = require('express-session'); app.use(session({ store: require('connect-session-knex')() })); 

我在这里有几个问题,如果你知道答案,我想回答:

每次在Nodejs中调用一个require ,是不是被称为dependency injection? 或者dependency injection的真正含义是什么?

我之所以问这个问题,是因为我一直在阅读关于Node的内容,而且我看到有人在谈论module或者module.export模式,我很迷惑, moduledependency相同吗?

所以,我所需要的只是关于dependency injection的一个清晰的解释,以及何时何地需要注入依赖关系。 。 。

dependency injection与正常的模块devise有些相反。 在正常的模块devise中,一个模块使用require()来加载所需的所有其他模块,目的是让调用者使用模块变得简单。 调用者可以只require()在您的模块,您的模块将加载所有其他所需的东西。

使用dependency injection,而不是模块加载所需的东西,调用者需要传递模块需要的东西(通常是对象)。 这可以使某些types的testing更容易,并且可以使testing目的更容易一些。

每次在Nodejs中调用一个require,是不是被称为dependency injection? 或者dependency injection的真正含义是什么?

不。当一个模块执行一个require()来加载它自己的依赖关系,而不是dependency injection。

我之所以问这个问题,是因为我一直在阅读关于Node的内容,而且我看到有人在谈论模块或者module.export模式,我很迷惑,模块和依赖相同吗?

一个模块与依赖不一样。 正常的模块devise允许你只require()模块,并得到一系列你可以使用的导出。 模块本身处理加载它自己的依赖关系(通常在模块内部使用require() )。

这里有几篇文章讨论了使用dependency injection的一些优缺点。 就我所能说的,主要优点是通过允许依赖对象(如数据库)更容易地被嘲弄来简化unit testing。

何时使用dependency injection

何时不适合使用dependency injection

为什么我们应该使用dependency injection


使用dependency injection的经典案例是模块依赖于数据库接口。 如果模块加载它自己的数据库,那么该模块本质上是硬连线到特定的数据库。 调用程序没有内置到模块中的体系结构来指定应使用哪种types的存储。

但是,如果模块设置为当调用程序加载并初始化模块时,它必须传入一个实现特定数据库API的对象,则调用者可以自由决定应使用哪种types的数据库。 任何符合API合同的数据库都可以使用。 但是,调用者的负担是挑选并加载特定的数据库。 在模块有默认使用的内置数据库的情况下,也可能会出现混合情况,但调用者可以提供自己的对象,如果在模块构造函数或模块初始化中提供了它们,将会使用它们。

想象这个代码。

 var someModule = require('pathToSomeModule'); someModule(); 

在这里,我们不是取决于名称,而是取决于该文件的path。 我们也每次都使用SAME文件。

让我们来看看angular度的方式(对于客户,我知道,忍受着我)

 app.controller('someCtrl', function($scope) { $scope.foo = 'bar'; }); 

我知道客户端js没有文件导入/导出,但这是你应该看看的基本概念。 这个控制器没有指定$ scopevariablesACTUALLY是什么,它只知道angular度给它一些CALLED $ scope。

这是控制反转这就像是说, 不要打电话给我,我会打电话给你

现在让我们用类似服务容器的方式实现我们的原始代码(有许多不同的解决scheme,容器不是唯一的select)

 // The only require statement var container = require('pathToContainer') var someModule = container.resolve('someModule'); someModule(); 

我们在这里完成了什么? 现在,我们只需要知道一件事情,容器(或者你select的任何抽象)。 我们不知道一些someModule实际上是什么,或者它的源文件是什么,只是这是我们从容器中得到的。 这样做的好处是,如果我们想要使用someModule的不同实现,只要符合与someModule相同的API,我们就可以在我们的整个应用程序中将其replace为一个地方。 容器。 现在每个调用someModule模块都会得到新的实现。 这个想法是,当你创build一个模块时,你需要定义你用来与之交互的api。 如果不同的实现都符合单个api(或者你写一个符合它的适配器),那么你可以换掉像肮脏的内衣实现,你的应用程序将工作。

这种做法不适合每个人,有些人讨厌在集装箱周围拖车。

我个人认为,我宁愿编写一个接口(实现之间一致的API),而不是编码到一个具体的实现。

node.js中的一个实例依赖注入

 // In a file, far, far, away module.exports = function(dependencyA, dependencyB) { dependencyA(); dependencyB(); } // In another file, the `caller` // This is where the actual, concrete implementation is stored var depA = someConcreteImplementation; var depB = someOtherConcreteImplementation; var someModule = require('pathToSomeModule'); someModule(depA, depB); 

这个缺点是现在调用者需要知道你的依赖关系是什么。 有些人为此感到安慰,喜欢它,其他人则认为这是一件麻烦事。

我个人比较喜欢这个下一个方法。

如果你不使用babel,或者在幕后改变了你的函数,你可以使用这种方法来获得angular度风格的参数parsing

http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript

然后你可以parsing你从require获得的function,而不是使用容器。

我用ts-node使用injection-js

npm i -P injection-js

它基于angular2注射器,所以注射非常简单。

这是来自README的打字稿版本。

 import 'reflect-metadata'; import { ReflectiveInjector, Injectable, Injector } from 'injection-js'; class Http {} @Injectable() class Service { constructor(private http: Http) {} } @Injectable() class Service2 { constructor(private injector: Injector) {} getService(): void { console.log(this.injector.get(Service) instanceof Service); } createChildInjector(): void { const childInjector = ReflectiveInjector.resolveAndCreate([ Service ], this.injector); } } const injector = ReflectiveInjector.resolveAndCreate([ Service, Http ]); console.log(injector.get(Service) instanceof Service);