最近忽然想到了一些事情,把之前对connect模块印象中的“中间件”这个东西,“整改”了过来。当时做代码,懒得去细分,用了些connect所套用的“中间件”方式。“中间件”,java的东西。我对java的东西是越来越有难用的感觉。
因为套用中间件,让我没时间去思考事件驱动。在nodejs模块中,就目前来看,中间件主要是解决逻辑分发的问题。大白话,就是把一个处理任务下的多个逻辑,分放到多个文件中。next()
函数在这里扮演了“拐点”的角色。
###next 有多么多余
next多少是“难看”和“多余”的。虽然一直用着不舒服,不过一直没时间多想。这几天,偏偏无聊,就想到了。对于js来讲,尤其nodejs,是有很好的分发血统的,那就是事件驱动。
在dom里,书写分发事件是一个家常便饭
elem1.addEventListener('click', fn1, false);
elem1.addEventListener('click', fn2, false);
elem1.addEventListener('click', fn3, false);
...
换算成多余的中间件就可以是这样
elem1.click(function() {// ...
next();
}, false);
elem1.click(function() {// ...
next();
}, false);
elem1.click(function() {// ...
next();
}, false);
...
这代码真心多余到爆了!!! java本就是个擅长“多余”的语言!!! 当然,java程序员也许会叫ta“命令模式”!!!
###next 有多么违反抽象
function () { ... next() }
function () { ... next() }
function () { ... next() }
...
next永远只告诉你,下面运行一大片代码,却永远无法准确的告诉你下面要做什么事情。有时候,你不得不怀疑,你是不是在写一大堆js版本的goto!!! 当代码变得庞大,这会造成可维护问题以及扩展问题。设计模式的核心是抽象,怎么做的毫不重要,做什么才是最重要的。
parse();
print();
logger();
###如何修改next
- 扔掉中间件,如果可以的话扔掉面向对象,使用FP。如C语言所说,程序的本质,只有数据结构和算法。
- 如果要分发,使用事件驱动 要编写自己的事件模型,只需要继承内核的events.EventEmitter,通过on挂载、emit触发。
function connect (req, res) {
res.on('parse', function () { // ...
res.emit('print');
});
res.on('print', function () { // ...
res.emit('logger');
});
res.on('logger', function () { // ...
res.emit('...');
});
...
}
这是一个很典型的connect中间件方式
function connect (/*f1, f2, ...*/) {
var middlewares = [];
for (var i = 0, len = arguments.length; i < len; i++) {
if(typeof arguments[i] === 'function') {
middlewares.push(arguments[i]);
}
}
return function (req, res) {
var self = this,
args = arguments,
i = 0;
/*
middlewares.forEach(function (middleware) {
middleware.apply(self, args);
});
*/
function next () {
var middleware = middlewares[i],
nextArgs;
if(typeof middleware === 'function') {
nextArgs = Array.prototype.slice.call(arguments);
nextArgs.push(next);
i++;
middleware.apply(self, nextArgs);
}
}
next.apply(self, args);
};
}
这就是违反了抽象、职责单一的原则。
一个函数,应该是完成工作的。看一下next方式的函数,都做了些什么:
function print () {
// ...
if (...) {...}
else { next() }
}
有许多时候,为了能实现跳转,你不得不在函数中写逻辑。 而这些逻辑恰恰是摧毁函数可用性的源头。 这很像在写goto,而我们知道c语言是严禁goto的。
function print () {
// ...
if (...) {...}
else { goto:log; }
}
print()的任务就是打印,不应该包含判断输出是否正确,下一个目标是不是A。
当你的代码容量膨胀到一定程度,你想在next这种组件上扩充的时候,你就要非常小心了。而且,当你拿到一些next代码的时候,你要理解作者的意图,恐怕是非常困难的。
if (...) {...}
else { next() }
if (...) {...}
else { emit('log') }
这有多大区别?用emit还得确切地指明下一步的操作,把两个本应相互独立的操作耦合起来了
思路,观点很好啊。一个函数只做一件事。就如作者说的一样,print函数就只负责打印,不要搞大的逻辑分支。 但是,next并不一定只应用于这一场景啊。如:
function print(data) {
console.log(data);
next();
}
我觉得这样使用next也是一种不错的编程思想。做完了print,就做下一个处理(可以是log)。 所以,我觉得代码用在好的地方才能体现它的思想价值,用在不好的地方(就如作者举的例子),就显得多余。
区别大了.
作为print(callback)这个函数,是原子级别的抽象。 对于next, 你将需要自己建立栈保存函数组,
function a (next) { print(next); }
请问你能告诉我控制器a打算干什么吗?
res.on('a', function () { prtint(emit('b')); })
我们把内容放大一下:
exports.index = c([
function (next) { parse(next); },
function (next) { print(next); },
function (next) { logger(next); },
]);
或者
exports.index = c([
parse(next),
print(next),
logger(next),
]);
exports.index = function (req, res) {
res.on('parse', parse(emit(res, 'print')));
res.on('print', function () { console.log('Hellow world!') });
res.on('print', function () { ... test(...); ... });
res.on('logger', logger(emit(res, '...')));
...
res.on('print', print(emit(res, 'logger')));
};
哪一种具备可读和易分发,我想一目了然
使用数据结构和算法来表示逻辑,而不是用对象。 使用独立的函数来封装逻辑,而不是用对象。
function Tree () { … } Tree.prototype.each = function (f) {}; Tree.prototype.next = function (f) {};
function each (tree, f) function next (tree, f)
下面的2个函数可以脱离关系,只要tree满足需要的结构。 上面的两个方法则必须要绑定在一起。
要扩展tree操作的时候,对于Tree,有时候需要考虑是否进行子对象的构建,以免Tree做了过多的事情。 而对于函数来讲,大家都是相互独立的,无需为其他外部因素考虑更多。
@tulayang 你是在用这种模式处理业务吗?我前面说了这种模式只适合处理管道,一有分支就不合适了,Connect用没问题,拿这种模式处理业务当然会觉得烂了,不然你拿你的事件驱动来处理Connect的场景,看得比Connect麻烦多少,不是这种模式恶劣而是用错了地方
我怎么有种哭都哭不出来的感觉。。。 这是在说https://github.com/senchalabs/connect 这个Connect么? 怎么感觉说的和这个Connect一点关系没有,所谓的Connect的中间件,也就是一个Filter, 就像一根管子,它只知道流进到它的数据是什么,也只知道它流出去数据是什么,它不知道数据从哪里流进来的,也不知道流到哪里去(就完整性来说,可以不流下去也可以流到处理污水的管子(Error Handler),它就是根直管子。 你想想你手中有100只各种颜色的管子,自己把两头拼起来做成一根长管子,可以用xx根拼成yy色的管子,怎么组装完全是用户的事,可以用10根拼成单色的长管子,也可以用3根拼成3种颜色的管子。
Connect中间件本身,要么是业务独立的,要么是业务自我完结的,@tulayang 说了那么多,又要耦合又要各种分支跳转,这是在讲一个中间件的内部实现方式么?而且,Connect的中间件,可以是一个小方法也可以是一个完整的subapp,可能是1行也可能是XXX万行,和什么FP有哪怕一点点关系???
楼主这是滥用event,比next还要糟糕。 中间件的方式是有可改进的地方,比如许多中间件其实是提供额外的api,在支持trait的语言中可以用trait实现,这样性能也更好。但是就当前的javascript来说,Connect的方式已经是最好的了,就算用Koa也是next。因为中间件的模型本来就是管道。