Javascript依赖注入&;浸入节点:需要vs构造函数注入
我是来自.NET世界的NodeJs开发新手 我在网上搜索Javascript中重新分级DI/DIP的最佳实践 在.NET中,我会在构造函数中声明依赖项,而在javascript中,我看到一种常见的模式是通过require语句在模块级声明依赖项 对我来说,当我使用require时,我会耦合到一个特定的文件,而使用构造函数来接收依赖项则更灵活 作为javascript的最佳实践,您建议做什么?(我在寻找架构模式,而不是IOC技术解决方案) 在网上搜索时,我发现了这篇博文(在评论中有一些非常有趣的讨论): 它很好地概括了我的冲突。 以下是博客文章中的一些代码,让您理解我所说的:Javascript依赖注入&;浸入节点:需要vs构造函数注入,javascript,.net,node.js,dependency-injection,dependency-inversion,Javascript,.net,Node.js,Dependency Injection,Dependency Inversion,我是来自.NET世界的NodeJs开发新手 我在网上搜索Javascript中重新分级DI/DIP的最佳实践 在.NET中,我会在构造函数中声明依赖项,而在javascript中,我看到一种常见的模式是通过require语句在模块级声明依赖项 对我来说,当我使用require时,我会耦合到一个特定的文件,而使用构造函数来接收依赖项则更灵活 作为javascript的最佳实践,您建议做什么?(我在寻找架构模式,而不是IOC技术解决方案) 在网上搜索时,我发现了这篇博文(在评论中有一些非常有趣的讨论
// team.js
var User = require('./user');
function getTeam(teamId) {
return User.find({teamId: teamId});
}
module.exports.getTeam = getTeam;
一个简单的测试如下所示:
// team.spec.js
var Team = require('./team');
var User = require('./user');
describe('Team', function() {
it('#getTeam', function* () {
var users = [{id: 1, id: 2}];
this.sandbox.stub(User, 'find', function() {
return Promise.resolve(users);
});
var team = yield team.getTeam();
expect(team).to.eql(users);
});
});
VS DI:
// team.js
function Team(options) {
this.options = options;
}
Team.prototype.getTeam = function(teamId) {
return this.options.User.find({teamId: teamId})
}
function create(options) {
return new Team(options);
}
测试:
关于你的问题:我不认为JS社区有一个普遍的做法。我在野外见过这两种类型,它们都需要修改(如or)和构造函数注入(通常使用专用DI容器)。然而,就个人而言,我认为不使用DI容器更适合JS。这是因为JS是一种具有。让我解释一下: 使用DI容器对所有内容强制构造函数注入。这会造成巨大的配置开销,主要原因有两个:
function fileTypeCounter(allFiles) {
// count file types
return fileTypes;
}
function getAllFilesInDir(dirname, callback) {
// recursively walk all folders and collect all files
// ...
callback(null, allFiles);
}
// now let's compose both functions
function getAllFileTypesInDir(dirname, callback) {
getAllFilesInDir(dirname, (err, allFiles) => {
callback(err, !err && fileTypeCounter(allFiles));
});
}
在中,将有状态IO与实际处理分离是一种常见的范例。例如,如果您正在编写用于计算文件夹中的文件类型的代码,则可以编写以下代码(尤其是当他/她来自一种到处强制类的语言时):
现在,如果您想测试这一点,您需要更改代码,以便注入一个假的fs
模块:
class FileTypeCounter {
constructor(fs) {
this.fs = fs;
}
countFileTypes(dirname, callback) {
this.fs.readdir(dirname, function (err) {
// ...
});
}
}
现在,使用您的类的每个人都需要将fs
注入构造函数。因为这很无聊,而且一旦你有了很长的依赖关系图,你的代码就会变得更复杂,所以开发人员发明了DI容器,他们可以在其中配置东西,而DI容器可以完成实例化
但是,仅仅编写纯函数怎么样
function fileTypeCounter(allFiles) {
// count file types
return fileTypes;
}
function getAllFilesInDir(dirname, callback) {
// recursively walk all folders and collect all files
// ...
callback(null, allFiles);
}
// now let's compose both functions
function getAllFileTypesInDir(dirname, callback) {
getAllFilesInDir(dirname, (err, allFiles) => {
callback(err, !err && fileTypeCounter(allFiles));
});
}
现在您有两个现成的超级通用功能,一个用于执行IO,另一个用于处理数据fileTypeCounter
是一个非常简单的测试工具GetAllFileIndir
是一项不纯净的任务,但这是一项如此常见的任务,您经常会发现它已经在其他人为它编写集成测试的地方出现了getAllFileTypesInDir
只是通过一点控制流来组合函数。这是集成测试的典型案例,您需要确保整个应用程序正常工作
通过在IO和数据处理之间分离代码,您将发现根本不需要注入任何东西。如果你不需要注射任何东西,那是个好迹象。纯函数是最容易测试的,并且仍然是在项目之间共享代码的最简单方式。在过去,我们从Java和.NET中了解到的DI容器并不存在。随着Node6的出现,ES6代理打开了这种容器的可能性——例如 因此,让我们将您的代码重写为现代ES6
class Team {
constructor ({ User }) {
this.User = user
}
getTeam (teamId) {
return this.User.find({ teamId: teamId })
}
}
以及测试:
import Team from './Team'
describe('Team', function() {
it('#getTeam', async function () {
const users = [{id: 1, id: 2}]
const fakeUser = {
find: function() {
return Promise.resolve(users)
}
}
const team = new Team({
User: fakeUser
})
const team = await team.getTeam()
expect(team).to.eql(users)
})
})
现在,使用Awilix,让我们编写我们的合成根:
这很简单;Awilix也可以处理对象生命周期,就像.NET/Java容器一样。这让您可以做一些很酷的事情,比如将当前用户注入到您的服务中,在每个http请求中插入您的服务一次,等等。谢谢您的详细回答!我必须更详细地研究函数式编程,看起来javascript非常适合它。你有什么可以看的示例库吗?这是我所知道的关于JavaScript函数式编程的最全面的介绍。
import Team from './Team'
describe('Team', function() {
it('#getTeam', async function () {
const users = [{id: 1, id: 2}]
const fakeUser = {
find: function() {
return Promise.resolve(users)
}
}
const team = new Team({
User: fakeUser
})
const team = await team.getTeam()
expect(team).to.eql(users)
})
})
import { createContainer, asClass } from 'awilix'
import Team from './Team'
import User from './User'
const container = createContainer()
.register({
Team: asClass(Team),
User: asClass(User)
})
// Grab an instance of Team
const team = container.resolve('Team')
// Alternatively...
const team = container.cradle.Team
// Use it
team.getTeam(123) // calls User.find()