我目前正在用nodejs创建一些实验项目。我已经用Spring编写了很多Java EE web应用程序,并且很欣赏它的依赖注入的便利性。
现在我很好奇:我如何使用node进行依赖注入?或者:我真的需要它吗?由于编程风格不同,是否存在替代概念?
我谈论的是简单的事情,像共享一个数据库连接对象,到目前为止,但我还没有找到一个满足我的解决方案。
简而言之,您不需要像c#/Java那样使用依赖注入容器或服务定位器。由于Node.js利用了module pattern
,所以没有必要执行构造函数或属性注入。虽然你仍然可以。
关于JS的伟大之处在于你可以修改任何东西来实现你想要的。这在测试时很有用。
看我那蹩脚的做作的例子。
MyClass.js
:
var fs = require('fs');
MyClass.prototype.errorFileExists = function(dir) {
var dirsOrFiles = fs.readdirSync(dir);
for (var d of dirsOrFiles) {
if (d === 'error.txt') return true;
}
return false;
};
MyClass.test.js
:
describe('MyClass', function(){
it('should return an error if error.txt is found in the directory', function(done){
var mc = new MyClass();
assert(mc.errorFileExists('/tmp/mydir')); //true
});
});
注意MyClass
是如何依赖于fs
模块的?正如@ShatyemShekhar所提到的,您确实可以像在其他语言中那样进行构造函数或属性注入。但在Javascript中不需要。
在这种情况下,您可以做两件事:
你可以存根fs.readdirSync
方法或者你可以返回一个完全不同的模块当你调用require
。
方法1:
var oldmethod = fs.readdirSync;
fs.readdirSync = function(dir) {
return ['somefile.txt', 'error.txt', 'anotherfile.txt'];
};
*** PERFORM TEST ***
*** RESTORE METHOD AFTER TEST ****
fs.readddirSync = oldmethod;
方法2:
var oldrequire = require
require = function(module) {
if (module === 'fs') {
return {
readdirSync: function(dir) {
return ['somefile.txt', 'error.txt', 'anotherfile.txt'];
};
};
} else
return oldrequire(module);
}
关键是利用Node.js和Javascript的强大功能。注意,我是一个CoffeeScript家伙,所以我的JS语法可能是不正确的地方。另外,我并不是说这是最好的方法,但它确实是一种方法。Javascript大师们或许可以提供其他解决方案。
更新:
这应该解决关于数据库连接的特定问题。我将创建一个单独的模块来封装数据库连接逻辑。像这样:
MyDbConnection.js
:(一定要选择一个更好的名字)
var db = require('whichever_db_vendor_i_use');
module.exports.fetchConnection() = function() {
//logic to test connection
//do I want to connection pool?
//do I need only one connection throughout the lifecyle of my application?
return db.createConnection(port, host, databasename); //<--- values typically from a config file
}
然后,任何需要数据库连接的模块都将包括您的MyDbConnection
模块。
SuperCoolWebApp.js
:
var dbCon = require('./lib/mydbconnection'); //wherever the file is stored
//now do something with the connection
var connection = dbCon.fetchConnection(); //mydbconnection.js is responsible for pooling, reusing, whatever your app use case is
//come TEST time of SuperCoolWebApp, you can set the require or return whatever you want, or, like I said, use an actual connection to a TEST database.
不要一字不差地照着这个例子做。这是一个蹩脚的例子,试图传达您利用module
模式来管理依赖项。希望这对你有帮助。
我知道这个帖子在这一点上相当老了,但我想我应该在这个问题上发表我的想法。TL;DR是由于JavaScript的无类型、动态特性,您实际上可以在不诉诸依赖注入(DI)模式或使用DI框架的情况下做很多事情。但是,随着应用程序变得越来越大、越来越复杂,DI绝对可以帮助提高代码的可维护性。
c#中的DI
要理解为什么在JavaScript中没有那么大的需要,看看c#这样的强类型语言是有帮助的。(向那些不懂c#的人道歉,但它应该很容易理解。)假设我们有一个应用程序,它描述了一辆汽车和它的喇叭。您将定义两个类:
class Horn
{
public void Honk()
{
Console.WriteLine("beep!");
}
}
class Car
{
private Horn horn;
public Car()
{
this.horn = new Horn();
}
public void HonkHorn()
{
this.horn.Honk();
}
}
class Program
{
static void Main()
{
var car = new Car();
car.HonkHorn();
}
}
这样写代码几乎没有什么问题。
Car
类与Horn
类中喇叭的特定实现紧密耦合。如果我们想改变汽车使用的喇叭类型,我们必须修改Car
类,即使它对喇叭的使用没有改变。这也使测试变得困难,因为我们不能将Car
类与它的依赖项Horn
类隔离开来。Car
类负责Horn
类的生命周期。在这样一个简单的例子中,这不是一个大问题,但在实际的应用程序中,依赖项会有依赖项,依赖项会有依赖项,等等。Car
类需要负责创建其依赖的整个树。这不仅复杂和重复,而且违反了"单一责任"。在班上。它应该专注于成为一辆汽车,而不是创建实例。- 没有办法重用相同的依赖实例。同样,这在这个小应用程序中并不重要,但是考虑一个数据库连接。您通常会在整个应用程序中共享一个实例。
现在,让我们重构它来使用依赖注入模式。
interface IHorn
{
void Honk();
}
class Horn : IHorn
{
public void Honk()
{
Console.WriteLine("beep!");
}
}
class Car
{
private IHorn horn;
public Car(IHorn horn)
{
this.horn = horn;
}
public void HonkHorn()
{
this.horn.Honk();
}
}
class Program
{
static void Main()
{
var horn = new Horn();
var car = new Car(horn);
car.HonkHorn();
}
}
我们在这里做了两件关键的事情。首先,我们介绍了Horn
类实现的接口。这让我们可以将Car
类编码到接口,而不是特定的实现。现在代码可以接受任何实现IHorn
的东西。其次,我们将号角实例化从Car
中取出并传递给它。这解决了上述问题,并将管理特定实例及其生命周期的任务留给了应用程序的主要功能。
这意味着我们可以在不触及Car
类的情况下为汽车引入一种新型喇叭:
class FrenchHorn : IHorn
{
public void Honk()
{
Console.WriteLine("le beep!");
}
}
主程序可以直接注入FrenchHorn
类的实例。这也极大地简化了测试。您可以创建一个MockHorn
类注入到Car
构造函数中,以确保您只测试单独的Car
类。
上面的例子显示了手动依赖注入。通常,DI是通过框架完成的(例如c#世界中的Unity或Ninject)。这些框架将通过遍历依赖关系图并根据需要创建实例,为您完成所有的依赖关系连接。
Node.js的标准方式
现在让我们看一下Node.js中的相同示例。我们可能会把代码分成3个模块:
// horn.js
module.exports = {
honk: function () {
console.log("beep!");
}
};
// car.js
var horn = require("./horn");
module.exports = {
honkHorn: function () {
horn.honk();
}
};
// index.js
var car = require("./car");
car.honkHorn();
因为JavaScript是无类型的,所以我们没有以前那样的紧密耦合。不需要接口(也不存在接口),因为car
模块只会尝试调用horn
模块导出的任何honk
方法。
此外,因为Node的require
缓存所有内容,模块本质上是存储在容器中的单例。在horn
模块上执行require
的任何其他模块都将获得完全相同的实例。这使得共享单例对象(如数据库连接)变得非常容易。
现在仍然存在car
模块负责获取其自己的依赖horn
的问题。如果您希望汽车使用不同的模块作为喇叭,则必须更改car
模块中的require
语句。这不是一个非常常见的事情,但它确实会导致测试问题。
人们处理测试问题的通常方法是使用代理。由于JavaScript的动态特性,proxyquire会拦截对require的调用,并返回您提供的任何存根/mock。
var proxyquire = require('proxyquire');
var hornStub = {
honk: function () {
console.log("test beep!");
}
};
var car = proxyquire('./car', { './horn': hornStub });
// Now make test assertions on car...
这对大多数应用程序来说已经足够了。如果它适用于你的应用,那就采用它。然而,根据我的经验,随着应用程序变得越来越大,越来越复杂,维护这样的代码变得越来越困难。
JavaScript中的DI
Node.js非常灵活。如果对上面的方法不满意,可以使用依赖注入模式编写模块。在这种模式中,每个模块导出一个工厂函数(或一个类构造函数)。
// horn.js
module.exports = function () {
return {
honk: function () {
console.log("beep!");
}
};
};
// car.js
module.exports = function (horn) {
return {
honkHorn: function () {
horn.honk();
}
};
};
// index.js
var horn = require("./horn")();
var car = require("./car")(horn);
car.honkHorn();
这与前面的c#方法非常相似,因为index.js
模块负责实例生命周期和连接。单元测试非常简单,因为您只需将模拟/存根传递给函数即可。同样,如果这对您的应用程序足够好,请使用它。
Bolus DI Framework
与c#不同的是,没有现成的标准DI框架来帮助你管理依赖关系。在npm注册表中有很多框架,但没有一个被广泛采用。其中许多选项已经在其他答案中被引用了。我对任何可用的选项都不是特别满意,所以我自己写了一个叫做bolus的选项。Bolus的设计与上面的DI风格编写的代码工作,并试图是非常干燥和非常简单。使用上面完全相同的car.js
和horn.js
模块,您可以用bolus重写index.js
模块:
// index.js
var Injector = require("bolus");
var injector = new Injector();
injector.registerPath("**/*.js");
var car = injector.resolve("car");
car.honkHorn();
基本思想是创建一个注入器。你在注入器中注册了所有的模块。然后你只需解决你所需要的。Bolus将遍历依赖关系图,并根据需要创建和注入依赖关系。在像这样的小示例中,您不会节省太多,但是在具有复杂依赖树的大型应用程序中,节省是巨大的。
Bolus支持许多漂亮的特性,比如可选的依赖项和测试全局变量,但是相对于标准的Node.js方法,我看到了两个关键的好处。首先,如果你有很多类似的应用,你可以为你的基类创建一个私有的npm模块,它会创建一个注入器,并在其中注册有用的对象。然后你的特定应用可以根据需要添加、覆盖和解析,就像AngularJS的注入器一样。其次,您可以使用bolus来管理依赖的各种上下文。例如,你可以使用中间件为每个请求创建一个子注入器,在注入器上注册用户id、会话id、日志记录器等,以及与之相关的任何模块。然后解决处理请求所需的内容。这为您提供每个请求的模块实例,并防止必须将记录器等传递给每个模块函数调用。
require()
和最近的ES模块(import
)在Node.js中管理依赖关系的一种方法,当然它是直观和有效的,但它也有它的局限性。
我的建议是看看目前Node.js可用的一些依赖注入容器,了解它们的优缺点。其中一些是:
- awilix
- injection-js
- bottlejs
- inversify
- node-dependency-injection
只是举几个例子。
现在真正的问题是,与简单的require()
或import
相比,使用Node.js DI容器可以实现什么?
优点:
- 更好的可测试性:模块接受依赖项作为输入
- 控制反转:决定如何在不触及应用程序主代码的情况下连接模块。
- 一个可定制的算法来解析模块:依赖项有"虚拟"标识符,通常不绑定到文件系统上的路径。
- 更好的可扩展性:由IoC和"虚拟"启用标识符。
- 其他可能的花哨的东西:<
- 异步初始化/gh>模块生命周期管理
- DI容器本身的可扩展性
- 可以轻松实现更高级的抽象(例如AOP)
缺点:
- 不同于Node.js的"体验":使用DI肯定会让你感觉偏离了Node的思维方式。
- 依赖项与其实现之间的关系并不总是明确的。依赖项可能在运行时被解析,并受到各种参数的影响。代码变得更加难以理解和调试
- 启动时间较慢
- 大多数DI容器不能很好地与像Browserify和Webpack这样的模块捆绑器一起工作。
与软件开发相关的任何事情一样,在DI或require()
/import
之间进行选择取决于您的需求、您的系统复杂性和您的编程风格。
我还编写了一个模块来完成此操作,它称为rewire。只需使用npm install rewire
,然后:
var rewire = require("rewire"),
myModule = rewire("./path/to/myModule.js"); // exactly like require()
// Your module will now export a special setter and getter for private variables.
myModule.__set__("myPrivateVar", 123);
myModule.__get__("myPrivateVar"); // = 123
// This allows you to mock almost everything within the module e.g. the fs-module.
// Just pass the variable name as first parameter and your mock as second.
myModule.__set__("fs", {
readFile: function (path, encoding, cb) {
cb(null, "Success!");
}
});
myModule.readSomethingFromFileSystem(function (err, data) {
console.log(data); // = Success!
});
我的灵感来自Nathan MacInnes的注射器,但使用了不同的方法。我不使用vm
来评估测试模块,事实上我使用节点自己的要求。这样,您的模块的行为就像使用require()
一样(除了您的修改)。也完全支持调试。
我正是为此目的构建了电解质。对于我来说,其他的依赖注入解决方案都太过侵入性了,扰乱全局require
是我特别不满的。
电解质包含模块,特别是那些导出"设置"功能的模块,如您在Connect/Express中间件中看到的。从本质上讲,这些类型的模块只是它们返回的对象的工厂。
例如,创建数据库连接的模块:
var mysql = require('mysql');
exports = module.exports = function(settings) {
var connection = mysql.createConnection({
host: settings.dbHost,
port: settings.dbPort
});
connection.connect(function(err) {
if (err) { throw err; }
});
return connection;
}
exports['@singleton'] = true;
exports['@require'] = [ 'settings' ];
你在底部看到的是注释,电解质使用它来实例化和注入依赖项,自动将应用程序的组件连接在一起。
创建数据库连接:
var db = electrolyte.create('database');
电解质传递遍历@require
的d依赖,并将实例作为参数注入导出函数。
关键是这是微创的。这个模块是完全可用的,独立于电解质本身。这意味着您的单元测试可以只测试下的模块,传入模拟对象,而不需要额外的依赖关系来重新连接内部。
当运行整个应用程序时,电解质会在模块间级别介入,将东西连接在一起,而不需要全局、单例或过多的管道。
我自己调查了一下。我不喜欢引入神奇的依赖库,因为它提供了劫持模块导入的机制。相反,我为我的团队提出了一个"设计指南",通过在我的模块中引入工厂函数导出来显式地说明哪些依赖可以被模拟。
我在参数和解构方面广泛使用了ES6的特性,以避免一些样板文件,并提供命名的依赖覆盖机制。
下面是一个例子:
import foo from './utils/foo';
import bob from './utils/bob';
// We export a factory which accepts our dependencies.
export const factory = (dependencies = {}) => {
const {
// The 'bob' dependency. We default to the standard 'bob' imp if not provided.
$bob = bob,
// Instead of exposing the whole 'foo' api, we only provide a mechanism
// with which to override the specific part of foo we care about.
$doSomething = foo.doSomething // defaults to standard imp if none provided.
} = dependencies;
return function bar() {
return $bob($doSomething());
}
}
// The default implementation, which would end up using default deps.
export default factory();
这里有一个使用
的例子import { factory } from './bar';
const underTest = factory({ $bob: () => 'BOB!' }); // only override bob!
const result = underTest();
请原谅那些不熟悉ES6语法的人。
我最近检查了这个线程,原因与OP大致相同-我遇到的大多数库都临时重写了require语句。我使用这种方法取得了不同程度的成功,所以我最终使用了以下方法。
在一个express应用的上下文中——我把app.js包装在一个bootstrap.js文件中:
var path = require('path');
var myapp = require('./app.js');
var loader = require('./server/services/loader.js');
// give the loader the root directory
// and an object mapping module names
// to paths relative to that root
loader.init(path.normalize(__dirname), require('./server/config/loader.js'));
myapp.start();
传递给加载器的对象映射是这样的:
// live loader config
module.exports = {
'dataBaseService': '/lib/dataBaseService.js'
}
// test loader config
module.exports = {
'dataBaseService': '/mocks/dataBaseService.js'
'otherService' : {other: 'service'} // takes objects too...
};
然后,而不是直接调用require…
var myDatabaseService = loader.load('dataBaseService');
如果加载器中没有别名,那么它将默认为常规的require。这有两个好处:我可以交换任何版本的类,并且它消除了这种需求在整个应用程序中使用相对路径名(所以如果我需要下面的自定义库)或在当前文件之上,我不需要遍历,并且require将根据相同的键缓存模块)。它还允许我在应用程序的任何点指定mock,而不是在立即的测试套件中。
为了方便,我刚刚发布了一个小的npm模块:
https://npmjs.org/package/nodejs-simple-loader
事实上,你可以在没有IoC容器的情况下测试你的node.js,因为JavaScript是一种真正的动态编程语言,你可以在运行时修改几乎所有的东西。
考虑以下内容:
import UserRepository from "./dal/user_repository";
class UserController {
constructor() {
this._repository = new UserRepository();
}
getUsers() {
this._repository.getAll();
}
}
export default UserController;
因此您可以在运行时重写组件之间的耦合。我认为我们应该致力于解耦我们的JavaScript模块。
实现真正解耦的唯一方法是删除对UserRepository
的引用:
class UserController {
constructor(userRepository) {
this._repository = userRepository;
}
getUsers() {
this._repository.getAll();
}
}
export default UserController;
这意味着你需要在其他地方进行对象组合:
import UserRepository from "./dal/user_repository";
import UserController from "./dal/user_controller";
export default new UserController(new UserRepository());
我喜欢将对象组合委托给IoC容器的想法。您可以在文章the current state of dependency inversion in JavaScript中了解更多关于这个想法的信息。本文试图揭穿一些"JavaScript IoC容器神话":
误解1:JavaScript中没有IoC容器的位置
误解2:我们不需要IoC容器,我们已经有了模块加载器!误解3:依赖倒置===注入依赖
如果你也喜欢使用IoC容器的想法,你可以看看InversifyJS。最新版本(2.0.0)支持许多用例:
<- 内核模块/gh>内核中间件
- 使用类、字符串或符号作为依赖标识符
- 注入常量
- 类构造函数的注入
- 工厂注入 汽车工厂
- 提供商注入(异步工厂)
- 激活处理程序(用于注入代理)
- 多注射 <
- 标记绑定/gh>
- 自定义标签装饰 <
- 命名绑定/gh><
- 上下文绑定/gh>
- 友好异常(例如循环依赖)
你可以在InversifyJS了解更多。
我为ES6开发了这个容器https://github.com/zazoomauro/node-dependency-injection
import {ContainerBuilder} from 'node-dependency-injection'
let container = new ContainerBuilder()
container.register('mailer', 'Mailer')
然后您可以设置,例如,选择在集装箱中运输:
import {ContainerBuilder} from 'node-dependency-injection'
let container = new ContainerBuilder()
container
.register('mailer', 'Mailer')
.addArgument('sendmail')
这个类现在更加灵活,因为您已经将传输的选择从实现中分离出来,并将其放入容器中。
现在邮件服务已经在容器中了,你可以把它作为其他类的依赖注入。如果你有一个像这样的NewsletterManager类:
class NewsletterManager {
construct (mailer, fs) {
this._mailer = mailer
this._fs = fs
}
}
export default NewsletterManager
定义newsletter tter_manager服务时,邮件服务还不存在。使用Reference类告诉容器在初始化时事通讯管理器时注入邮件服务:
import {ContainerBuilder, Reference, PackageReference} from 'node-dependency-injection'
import Mailer from './Mailer'
import NewsletterManager from './NewsletterManager'
let container = new ContainerBuilder()
container
.register('mailer', Mailer)
.addArgument('sendmail')
container
.register('newsletter_manager', NewsletterManager)
.addArgument(new Reference('mailer'))
.addArgument(new PackageReference('fs-extra'))
你也可以用配置文件如Yaml、Json或JS文件来设置容器
由于各种原因可以编译服务容器。这些原因包括检查任何潜在的问题,如循环引用和使容器更高效。
container.compile()
这取决于应用程序的设计。显然,你可以做一个类似java的注入,创建一个类的对象,并在构造函数中传递依赖项,就像这样。
function Cache(store) {
this._store = store;
}
var cache = new Cache(mysqlStore);
如果你没有在javascript中做OOP,你可以创建一个init函数来设置一切。
然而,还有另一种方法可以采用,这种方法在基于事件的系统(如node.js)中更常见。如果您可以将应用程序建模为仅(大多数时候)对事件进行操作,那么您所需要做的就是设置所有内容(我通常通过调用init函数来完成)并从存根发出事件。这使得测试相当容易和可读。
我一直很喜欢IoC概念的简单性——"你不需要知道任何关于环境的事情,当你需要的时候会有人打电话给你">
但是我看到的所有IoC实现都做了完全相反的事情——它们用更多的东西使代码变得混乱。所以,我创建了我自己的IoC,它可以像我希望的那样工作-它在90%的时间里保持隐藏和不可见.
用于MonoJS web框架http://monojs.org
我说的是简单的事情,比如共享一个数据库连接对象,所以3 .迄今为止,我还没有找到一个使我满意的解决办法。
它是这样做的- register组件一次配置
app.register 'db', ->
require('mongodb').connect config.dbPath
并在任何地方使用
app.db.findSomething()
您可以在这里看到完整的组件定义代码(与DB连接和其他组件)https://github.com/sinizinairina/mono/blob/master/mono.coffee
这是你唯一需要告诉IoC做什么的地方,之后所有这些组件将自动创建和连接,你不再需要在应用程序中看到IoC特定的代码。
IoC本身https://github.com/alexeypetrushin/miconjs
我认为在Nodejs中我们仍然需要依赖注入,因为它放松了服务之间的依赖,使应用程序更清晰。
受Spring框架的启发,我也实现了自己的模块来支持Nodejs中的依赖注入。我的模块还能够检测code changes
和auto reload
服务,而无需重新启动应用程序。
访问我的项目:Buncha - IoC容器
谢谢!
Node.js和其他平台一样需要DI。如果你正在构建一些大的东西,DI将使模拟代码的依赖关系和彻底测试代码变得更容易。
例如,你的数据库层模块不应该只在你的业务代码模块中被要求,因为当单元测试这些业务代码模块时,dao将加载并连接到数据库。 一种解决方案是将依赖项作为模块参数传递:module.exports = function (dep1, dep2) {
// private methods
return {
// public methods
test: function(){...}
}
}
通过这种方式,可以轻松自然地模拟依赖关系,您可以专注于测试代码,而无需使用任何棘手的第三方库。
还有其他解决方案(百老汇、建筑师等)可以帮助你解决这个问题。尽管它们可能比你想要的做得更多,或者使用更多的杂物。
我在回答我自己的DI模块上的一个问题时发现了这个问题,这个问题问为什么需要一个DI系统来进行NodeJS编程。
答案显然倾向于这个线程中给出的答案:这取决于。这两种方法都有利弊,阅读这个问题的答案可以很好地了解它们。
因此,这个问题的真正答案应该是,在某些情况下,您会使用DI系统,而在其他情况下则不会。
也就是说,作为一名开发人员,你想要的是不要重复自己,不要在不同的应用程序中重用你的服务。
这意味着我们应该编写可以在DI系统中使用但不绑定到DI库的服务。对我来说,这意味着我们应该这样编写服务:
module.exports = initDBService;
// Tells any DI lib what it expects to find in it context object
// The $inject prop is the de facto standard for DI imo
initDBService.$inject = ['ENV'];
// Note the context object, imo, a DI tool should bring
// services in a single context object
function initDBService({ ENV }) {
/// actual service code
}
这样你的服务工作,无论你使用或
我使用。net, PHP和Java工作了很长时间,所以我想在NodeJS中也有一个方便的依赖注入。人们说内置的DI在NodeJS中已经足够了,因为我们可以用Module获得它。但我不太满意。我想保留一个模块,而不是一个类。此外,我希望DI能够完全支持模块生命周期管理(单例模块、瞬态模块等),但是使用Node模块时,我不得不经常编写手工代码。最后,我想让Unit Test更简单。这就是为什么我为自己创建了一个依赖注入。
如果你正在寻找一个DI,给它一个尝试。可以在这里找到:https://github.com/robo-creative/nodejs-robo-container。这是有完整记录的。本文还讨论了依赖注入的一些常见问题,以及如何以面向对象的方式解决这些问题。希望能有所帮助。
TypeDI是这里提到的最甜蜜的,看看TypeDI
中的代码import "reflect-metadata";
import {Service, Container} from "typedi";
@Service()
class SomeClass {
someMethod() {
}
}
let someClass = Container.get(SomeClass);
someClass.someMethod();
看下面的代码:
import {Container, Service, Inject} from "typedi";
// somewhere in your global app parameters
Container.set("authorization-token", "RVT9rVjSVN");
@Service()
class UserRepository {
@Inject("authorization-token")
authorizationToken: string;
}
看看dips(一个简单但功能强大的Node.js依赖注入和实体(文件)管理框架)
https://github.com/devcrust/node-dips
我认为其他文章在使用DI方面做得很好。对我来说,原因是
在不知道路径的情况下注入依赖项。这意味着,如果你改变了一个模块在磁盘上的位置,或者与另一个模块交换,你不需要触及依赖于它的每个文件。
它使模拟依赖关系以进行测试变得容易得多,而不用重写全局
require
函数的痛苦。它帮助你组织和推理你的应用程序作为松散耦合的模块。
但是我真的很难找到一个我和我的团队可以轻松采用的DI框架。所以我最近基于这些特性构建了一个名为deppie的框架
- 可以在几分钟内学会的最小API
- 不需要额外的代码/配置/注释
- 一对一直接映射到
require
模块 - 可以部分使用现有代码
应该像这样灵活而简单:
var MyClass1 = function () {}
var MyClass2 = function (myService1) {
// myService1.should.be.instanceof(MyClass1);
}
container.register('myService1', MyClass1);
container.register('myService2', MyClass2, ['myService1']);
我写过一篇关于node.js依赖注入的文章。
- 管理你的服务-node.js依赖注入
- 包文档在这里
我希望它能帮到你。
我开发了一个库,它以一种简单的方式处理依赖注入,从而减少了样板代码。每个模块由一个唯一的名称和一个控制器函数定义。控制器的参数反映了模块的依赖关系。
阅读更多关于KlarkJS的信息
简短的例子:
KlarkModule(module, 'myModuleName1', function($nodeModule1, myModuleName2) {
return {
log: function() { console.log('Hello from module myModuleName1') }
};
});
myModuleName1
为模块名。$nodeModule1
是node_module
的外部库。名称解析为node-module1
。前缀$
表示是外部模块。myModuleName2
为内部模块名。- 控制器的返回值是其他内部模块定义参数
myModuleName1
时使用的。
要很好地测试应用程序,大多数时候最好使用反转控制工具在运行/测试时注入所需的对象。因此,最好不要在模块中直接使用require或import。相反,调用您的DI容器来获取所需的对象。
如果您不想使用第三方库,您可以通过创建自定义DI容器来模仿IoC工具的行为。在测试时,您可以模拟DI容器并注入用于测试目的的假模块。下面是一个自定义DI容器的例子。这个版本的容器不支持接口。
myDependecy.js
const myDependecy = {};
export default myDependecy;
myDependecy.myTestFunction = () => {
console.log("this is as test function.");
};
diContainer.js
import myDependecy from "./myDependecy.js";
const diContainer = {};
export default diContainer;
diContainer.myDependecy = myDependecy;
myModule.js
import diContainer from "./diContainer.js";
function myFunction() {
diContainer.myDependecy.myTestFunction();
}
我最近创建了一个叫做circuitbox的库,它允许你在node.js中使用依赖注入。与我见过的许多基于依赖项查找的库相比,它实现了真正的依赖项注入。Circuitbox还支持异步创建和初始化例程。下面是一个例子:
假设以下代码位于一个名为consoleMessagePrinter.js的文件
'use strict';
// Our console message printer
// deps is injected by circuitbox with the dependencies
function ConsoleMessagePrinter(deps) {
return {
print: function () {
console.log(deps.messageSource.message());
}
};
}
module.exports = ConsoleMessagePrinter;
假设以下内容位于文件main.js
'use strict';
// our simple message source
// deps is injected by circuitbox with the dependencies
var simpleMessageSource = function (deps) {
return {
message: function () {
return deps.message;
}
};
};
// require circuitbox
var circuitbox = require('../lib');
// create a circuitbox
circuitbox.create({
modules: [
function (registry) {
// the message to be used
registry.for('message').use('This is the message');
// define the message source
registry.for('messageSource').use(simpleMessageSource)
.dependsOn('message');
// define the message printer - does a module.require internally
registry.for('messagePrinter').requires('./consoleMessagePrinter')
.dependsOn('messageSource');
}
]
}).done(function (cbx) {
// get the message printer and print a message
cbx.get('messagePrinter').done(function (printer) {
printer.print();
}, function (err) {
console.log('Could not recieve a printer');
return;
});
}, function (err) {
console.log('Could not create circuitbox');
});
Circuitbox允许您定义组件并将它们的依赖声明为模块。一旦初始化,它就允许您检索组件。Circuitbox自动注入目标组件所需的所有组件,并将其提供给您使用。
项目处于alpha版本。欢迎您的评论、想法和反馈。
希望有帮助!