设计模式之“中间件模式”

实际场景

在日常的开发过程中,我们在编写业务代码时候,无法避免有些业务逻辑复杂而导致业务代码写得又长又乱。有些逻辑像一个过程,在不同的节点需要做不同的操作。

比如,我们在开发的过程中经常会遇到数据提交这样一个场景。我们的目的是数据提交,但是在提交之前,我们需要对数据进行验证,验证正确之后,对数据发送进行上报,上报之后才是我们的目标操作提交数据。提交数据之后我们还需要跳转到提交成功的页面。这时候我们一般的做法会是这样:

if(//验证数据){

  //上报数据操作

  //提交数据操作

  //跳转成功页面

}

这时候我们会将整个流程的代码糅合在一起,如果代码简单一点还好,但是如果每一个步骤都有大量的逻辑操作,估计会让人抓狂。

解决方案

对于这一类的流程事件,我们可以采用分解这些事件,当需要用到这些事件操作时,我们将操作插入到核心事件完成所需要的不同步骤中。我们通过下面的方式来实现提交的功能:

Function.prototype.before = function(fn){
    var self = this;
    return function(){
        var res = fn.call(this);
        if(res){
            self.call(this,arguments);
        }
    }
};

Function.prototype.after = function(fn){
    var self = this;
    return function(){
        self.call(this,arguments);
        fn.call(this);
    }
};

function report(){
    console.log('上报数据');
    return true;
}

function validate(){
    console.log('验证数据');
    if( + new Date()%2 == 0){
        return true;
    }else{
        return false;
    }    
}

function submit(){
    console.log('提交数据');
}

function goback(){
    console.log('返回首页');
}

submit.before(report).before(validate).after(goback)();

通过上面的代码,我们将各个阶段的业务给分解开来,这样做的好处很明显,我们只要关注各个阶段的代码实现,最后将各个阶段通过管道式的方式拼装起来。有利于我们代码逻辑的解耦符合我们高内聚低耦合的原则。同时,各部分的代码又独立存在,当其他业务逻辑需要用到的时候,我们只需要把需要的部分取出来,拼装在需要的逻辑上面就可以了。这又有利于代码的复用。

但是,上面的代码又有两个问题

1、一串长长的链式调用,不方便维护者理解

2、如何before或者after的参数是一个异步操作的话,又需要做一些patch

有没有其他的方法来实现既能隔离业务,又能方便地使用呢。我们来看express的实现方式

Express中间件的实现

我们来看express的实现方法

 

var express = require('express');
var app = express();
 
app.use(function(req, res, next) {
  console.log('数据统计');
  next();//执行权利传递给
});

app.use(function(req, res, next) {
  console.log('日志统计');
  next();
});

app.get('/', function(req, res, next) {
  res.send('Hello World!');
});

app.listen(3000);
//整个请求处理过程就是先数据统计、日志统计,最后返回一个Hello World!

 

上图的运作流程

从上图来看,每一个管道都是一个中间件,每个中间件通过next方法传递执行权给下一个中间件,express就是一个收集并调用各种中间件的容器。

中间件就是一个函数,通过expressuse方法接收中间件,每个中间件有express传入的reqresnext参数。如果要把请求传递给下一个中间件必须使用 next() 方法。当调用res.send方法则此次请求结束,node直接返回请求给客户,但是若在res.send方法之后调用next方法,整个中间件链式调用还会往下执行,因为当前hello world所处的函数也是一块中间件,而res.send只是一个方法用于返回请求。

借用中间件实现

我们可以借用中间件思想来分解我们的前端业务逻辑,通过next方法层层传递给下一个业务。

代码如下:

var MidWare = function(){
    this.cache = [];
    this.options = {}
}

MidWare.prototype.use = function(fn){
    if(typeof fn !== 'function'){
        console.log('need a function');
        return false;
    }
    this.cache.push(fn);
    return this;
}

MidWare.prototype.next = function(argument){
    if(this.midwares && this.midwares.length > 0){
        var ware = this.midwares.shift();
        ware.call(this,this.options || {}, this.next.bind(this))
    }
};

MidWare.prototype.handleRequest = function(options){
  this.midwares = this.cache.map(function(fn){
    return fn;
  });
  this.options = options;//缓存数据
  this.next();
}

var submitForm = new MidWare();

//验证
submitForm.use(function(options, next){
    console.log('验证数据');
    next();
})

//上报
submitForm.use(function(options, next){
    setTimeout(function(){
        console.log('上报数据');
        next();
    }, 3000)
    
})

//提交数据
submitForm.use(function(options, next){
    console.log('提交数据');
    next();
})

//返回首页
submitForm.use(function(options, next){
    console.log('返回首页');
})

submitForm.handleRequest();

 

posted @ 2017-05-08 18:12  CaiBoBo  阅读(5476)  评论(0编辑  收藏  举报