10 职责链模式
职责链模式的定义:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止;
职责链模式最大优点:
请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系;
10.1 职责链模式实例
假设一个售卖手机的电商网站,经过分别交纳 500 元定金和 200 元定金的两轮预定后(订单已在此时生成),现在已经到了正式购买的阶段。公司针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过 500 元定金的用
户会收到 100 元的商城优惠券, 200 元定金的用户可以收到 50 元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到;
相关字段定义:
-
orderType
表示订单类型,code 的值为 1 的时候是 500 元定金用户,为 2 的时候是 200 元定金用户,为 3 的时候是普通购买用户; -
pay
:表示用户是否已经支付定金,值为 true 或者 false, 虽然用户已经下过 500 元定金的订单,但如果他一直没有支付定金,现在只能降级进入普通购买模式; -
stock
:表示当前用于普通购买的手机库存数量,已经支付过 500 元或者 200 元定金的用户不受此限制;
这个流程代码如下:
var order = function( orderType, pay, stock ){
if ( orderType === 1 ){ // 500 元定金购买模式
if ( pay === true ){ // 已支付定金
console.log( '500 元定金预购, 得到 100 优惠券' );
}else{ // 未支付定金,降级到普通购买模式
if ( stock > 0 ){ // 用于普通购买的手机还有库存
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
}
else if ( orderType === 2 ){ // 200 元定金购买模式
if ( pay === true ){
console.log( '200 元定金预购, 得到 50 优惠券' );
}else{
if ( stock > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
}
else if ( orderType === 3 ){
if ( stock > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
}
};
order( 1 , true, 500); // 输出: 500 元定金预购, 得到 100 优惠券
此代码阅读起来很难,当项目运行后,若增加其他优惠政策,修改起来也很困难;
10.2 用职责链模式重构代码
先把 500 元订单、 200 元订单以及普通购买分成 3个函数;接下来把 orderType、 pay、 stock
这 3 个字段当作参数传递给 500 元订单函数,如果该函数不符合处理条件,则把这个请求传递给后面的 200 元订单函数,如果 200 元订单函数依然不能处理该请求,则继续传递请求给普通购买函数,代码如下:
// 500 元订单
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500 元定金预购, 得到 100 优惠券' );
}else{
order200( orderType, pay, stock ); // 将请求传递给 200 元订单
}
};
// 200 元订单
var order200 = function( orderType, pay, stock ){
if ( orderType === 2 && pay === true ){
console.log( '200 元定金预购, 得到 50 优惠券' );
}else{
orderNormal( orderType, pay, stock ); // 将请求传递给普通订单
}
};
// 普通购买订单
var orderNormal = function( orderType, pay, stock ){
if ( stock > 0 ){
console.log( '普通购买, 无优惠券' );
}else{
console.log( '手机库存不足' );
}
};
// 测试:
order500( 1 , true, 500); // 输出: 500 元定金预购, 得到 100 优惠券
order500( 1, false, 500 ); // 输出:普通购买, 无优惠券
order500( 2, true, 500 ); // 输出: 200 元定金预购, 得到 500 优惠券
order500( 3, false, 500 ); // 输出:普通购买, 无优惠券
order500( 3, false, 0 ); // 输出:手机库存不足
此代码相对于上一个实现逻辑更加清晰,不过请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函
数之中(如其中的 order200 和 order500 耦合在一起),当增加其他优惠时,必须修改整条链,这违反了开放-封闭原则;
2.10.3 灵活可拆分的职责链节点
- 改写一下 3 种购买模式的节点函数,约定若某个节点不能处理请求,则返回一个特定的字符串
nextSuccessor
来表示该请求需要继续往后面传递,代码如下:
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500 元定金预购,得到 100 优惠券' );
}else{
return 'nextSuccessor'; // 不用知道下一个节点是谁,反正把请求往后面传递
}
};
var order200 = function( orderType, pay, stock ){
if ( orderType === 2 && pay === true ){
console.log( '200 元定金预购,得到 50 优惠券' );
}else{
return 'nextSuccessor'; // 不用知道下一个节点是谁,反正把请求往后面传递
}
};
var orderNormal = function( orderType, pay, stock ){
if ( stock > 0 ){
console.log( '普通购买,无优惠券' );
}else{
console.log( '手机库存不足' );
}
};
- 再定义一个构造函数 Chain ,在
new Chain
的时候传递的参数即为需要被包装的函数, 同时还拥有一个实例属性this.successor
,表示在链中的下一个节点:
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 ); // 输出: 500 元定金预购,得到 100 优惠券
chainOrder500.passRequest( 2, true, 500 ); // 输出: 200 元定金预购,得到 50 优惠券
chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足
- 自由灵活地增加、移除和修改链中的节点顺序,若新增 300 元的优惠政策,修改如下:
var order300 = function(){ // 具体实现略 };
chainOrder300= new Chain( order300 );
chainOrder500.setNextSuccessor( chainOrder300);
chainOrder300.setNextSuccessor( chainOrder200);
10.4 异步的职责链
实际开发中经常会遇到一些异步的问题,比如在节点函数中发起一个 ajax 异步请求,异步请求返回的结果才能决定是否继续在职责链中 passRequest
,因而 Chain 新增原型方法 Chain.prototype.next
,如下:
Chain.prototype.next= function(){
return this.successor && this.successor.passRequest.apply( this.successor, arguments );
};
10.5 职责链模式的优缺点
职责链模式的优点:
- 使用了职责链模式之后,链中的节点对象可以灵活地拆分重组,如增加和删除节点;
- 职责链模式还可以手动指定起始节点,请求并不是非得从链中的第一个节点开始传递;
职责链模式的缺点:
职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避免过长的职责链带来的性能损耗;
10.6 用 AOP (面向切面编程)实现职责链
利用 JavaScript 的函数式特性,有一种更加方便的方法来创建职责链;下面改写之前高阶函数中的Function.prototype.after
函数,使得第一个函数返回 nextSuccessor
时,将请求继续传递给下一个函数,其中约定返回字符串 nextSuccessor
或者 false
中的一个;
Function.prototype.after = function( fn ){
var self = this;
return function(){
var ret = self.apply( this, arguments );
if ( ret === 'nextSuccessor' ){
return fn.apply( this, arguments );
}
return ret;
}
};
var order = order500yuan.after( order200yuan ).after( orderNormal );
order( 1, true, 500 ); // 输出: 500 元定金预购,得到 100 优惠券
order( 2, true, 500 ); // 输出: 200 元定金预购,得到 50 优惠券
order( 1, false, 500 ); // 输出:普通购买,无优惠券
10.7 职责链模式小结
职责链模式可以很好地帮助我们管理代码,降低发起请求的对象和处理请求的对象之间的耦合性。职责链中的节点数量和顺序是可以自由变化的,我们可以在运行时决定链中包含哪些节点;职责链模式的应用有很多,如作用域链、原型链、 DOM 节点的事件冒泡等,职责链模式还可以和组合模式结合在一起,用来连接部件和父部件,或是提高组合对象的效率。
系列链接
- JavaScript 设计模式(上)——基础知识
- JavaScript 设计模式(中)——1.单例模式
- JavaScript 设计模式(中)——2.策略模式
- JavaScript 设计模式(中)——3.代理模式
- JavaScript 设计模式(中)——4.迭代器模式
- JavaScript 设计模式(中)——5.发布订阅模式
- JavaScript 设计模式(中)——6.命令模式
- JavaScript 设计模式(中)——7.组合模式
- JavaScript 设计模式(中)——8.模板方法模式
- JavaScript 设计模式(中)——9.享元模式
- JavaScript 设计模式(中)——10.职责链模式
- JavaScript 设计模式(中)——11. 中介者模式
- JavaScript 设计模式(中)——12. 装饰者模式
- JavaScript 设计模式(中)——13.状态模式
- JavaScript 设计模式(中)——14.适配器模式
- JavaScript 设计模式(下)——设计原则
- JavaScript 设计模式练习代码
本文主要参考了《JavaScript设计模式和开发实践》一书