职责链模式简述
职责连是由多个不同的对象组成的,有发送者跟接收者,分别负责信息的发送跟接收,其中,链中第一个对象是 职责连是由多个不同的对象组成的,发送者是发送请求的对象,接收者接收请求并且对其进行处理或传递的对象。基本流程如下:
- 发送者知道链中的第一个接收者,它向这个接收者发送该请求。
- 每一个接收者都对请求进行分析,然后要么处理它,要么它往下传递。
- 每一个接收者知道其他的对象只有一个,即它在链中的下家(successor)。
- 如果没有任何接收者处理请求,那么请求会从链中离开。
职责链模式是个链式结构,请求在链中的节点之间依次传递,直到有一个对象能处理该请求为止。如果没有任何对象处理该请求的话,那么请求就会从链中离开。
实战
以电商网站抽奖为例,规则如下:
- 用户充值500元,可以100%中奖100元红包
- 用户充值200元,可以100%中奖20元红包
- 用户不充值,也可以抽奖,但概率极低。
- 用户充值失败,按不充值处理。
常规实现
首先需要定义几个字段:
- orderType:充值类型。如果值为1的话,说明是充值500元的用户,如果为2的话,说明是充值200元的用户,如果是3的话,说明是没有充值的用户。
- isPay:是否已经成功充值了。 如果该值为true的话,说明已经成功充值了,否则的话说明没有充值成功;就当作普通用户来购买。
- count:表示数量。普通用户抽奖,如果数量有的话,就可以拿到优惠卷,否则的话,不能拿到优惠卷。
实现如下:
var order = function(orderType,isPay,count) { if(orderType == 1) { // 用户充值500元 if(isPay == true) { // 如果充值成功的话,100%中奖 console.log("亲爱的用户,您中奖了100元红包了"); }else { // 充值失败,就当作普通用户来处理中奖信息 if(count > 0) { console.log("亲爱的用户,您已抽到10元优惠卷"); }else { console.log("亲爱的用户,请再接再厉哦"); } } }else if(orderType == 2) { // 用户充值200元 if(isPay == true) { // 如果充值成功的话,100%中奖 console.log("亲爱的用户,您中奖了20元红包了"); }else { // 充值失败,就当作普通用户来处理中奖信息 if(count > 0) { console.log("亲爱的用户,您已抽到10元优惠卷"); }else { console.log("亲爱的用户,请再接再厉哦"); } } }else if(orderType == 3) { // 普通用户来处理中奖信息 if(count > 0) { console.log("亲爱的用户,您已抽到10元优惠卷"); }else { console.log("亲爱的用户,请再接再厉哦"); } }};复制代码
如上代码,虽然实现了需求,但存在的问题也比较突出: 一、业务逻辑代码耦合度太高,如果想增加条件,比如充值300可以中奖150元红包,这时候就很容易改出问题 二、冗余代码太多,普通用户抽奖的代码是可以单独抽离出来的
职责链实现
function order500(orderType,isPay,count){ if(orderType == 1 && isPay == true) { console.log("亲爱的用户,您中奖了100元红包了"); }else { //我不知道下一个节点是谁,反正把请求往后面传递 return "nextSuccessor"; }};function order200(orderType,isPay,count) { if(orderType == 2 && isPay == true) { console.log("亲爱的用户,您中奖了20元红包了"); }else { //我不知道下一个节点是谁,反正把请求往后面传递 return "nextSuccessor"; }};function orderNormal(orderType,isPay,count){ // 普通用户来处理中奖信息 if(count > 0) { console.log("亲爱的用户,您已抽到10元优惠卷"); }else { console.log("亲爱的用户,请再接再厉哦"); }}// 下面需要编写职责链模式的封装构造函数方法var Chain = function(fn){ this.fn = fn; this.successor = null;};Chain.prototype.setNextSuccessor = function(successor){ return this.successor = successor;}// 把请求往下传递Chain.prototype.passRequest = function(){ var ret = this.fn.apply(this,arguments); if(ret === 'nextSuccessor') { return this.successor && this.successor.passRequest.apply(this.successor,arguments); } return ret;}//现在我们把3个函数分别包装成职责链节点:var chainOrder500 = new Chain(order500);var chainOrder200 = new Chain(order200);var chainOrderNormal = new Chain(orderNormal);// 然后指定节点在职责链中的顺序chainOrder500.setNextSuccessor(chainOrder200);chainOrder200.setNextSuccessor(chainOrderNormal);//最后把请求传递给第一个节点:chainOrder500.passRequest(1,true,500); // 亲爱的用户,您中奖了100元红包了chainOrder500.passRequest(2,true,500); // 亲爱的用户,您中奖了20元红包了chainOrder500.passRequest(3,true,500); // 亲爱的用户,您已抽到10元优惠卷 chainOrder500.passRequest(1,false,0); // 亲爱的用户,请再接再厉哦复制代码
如上代码: 一、分别编写order500,order200,orderNormal三个函数,处理自己的业务逻辑,如果自己的函数不能处理的话,就返回字符串nextSuccessor 往后面传递 二、封装Chain构造函数,接收fn做为参数,且带有属性successor。实例化后的Chain类型的对象,就像一个一个独立存在的节点。 三、Chain构造函数原型上有2个方法,分别是setNextSuccessor 和 passRequest。setNextSuccessor 方法指定了节点在职责链中的顺序,passRequest方法是将请求转移到职责链的下一个节点。
如上文提到的,假设需要实现“充值300中奖150元”,我们可以编写order300这个函数,通过Chain包装起来,指定在职责链中的顺序即可。业务逻辑的代码不需要任何处理。
现在又有一个问题,我们在开发中经常会碰到ajax异步请求,如果我们用上面的做法,就不生效了。由此,便引出了异步的职责链来解决这个问题。我们给Chain类再增加一个原型方法Chain.prototype.next,表示手动传递请求给职责链中的一下个节点。
function Fn1() { console.log(1); return "nextSuccessor";}function Fn2() { console.log(2); var self = this; setTimeout(function(){ self.next(); },1000);}function Fn3() { console.log(3);}// 下面需要编写职责链模式的封装构造函数方法var Chain = function(fn){ this.fn = fn; this.successor = null;};Chain.prototype.setNextSuccessor = function(successor){ return this.successor = successor;}// 把请求往下传递Chain.prototype.passRequest = function(){ var ret = this.fn.apply(this,arguments); if(ret === 'nextSuccessor') { return this.successor && this.successor.passRequest.apply(this.successor,arguments); } return ret;}Chain.prototype.next = function(){ return this.successor && this.successor.passRequest.apply(this.successor,arguments);}//现在我们把3个函数分别包装成职责链节点:var chainFn1 = new Chain(Fn1);var chainFn2 = new Chain(Fn2);var chainFn3 = new Chain(Fn3);// 然后指定节点在职责链中的顺序chainFn1.setNextSuccessor(chainFn2);chainFn2.setNextSuccessor(chainFn3);chainFn1.passRequest(); // 打印出1,2 过1秒后 会打印出3复制代码
如上代码,当执行到F2时,遇到了定时器异步函数,当定时器执行结束后,调用了next方法,手动将请求交给职责链中的下一个节点,因此过了一秒后,会打印3。
总结
优点
- 解耦了请求发送者和度个接收者之间的复杂关系,不需要知道链中哪个节点能处理你的请求,只需要把请求传递到第一个节点即可。
- 链中的节点对象可以灵活地拆分重组,增加或删除一个节点,或者改变节点的位置都是很简单的事情。
- 我们还可以手动指定节点的起始位置,并不是说非得要从其实节点开始传递的.
缺点
职责链模式中多了一点节点对象,可能在某一次请求过程中,大部分节点没有起到实质性作用,他们的作用只是让请求传递下去,从性能方面考虑,避免过长的职责链提高性能。