设计模式之职责链模式
什么是职责链模式
职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。职责链模式的名字非常形象,一系列可能会处理请求的对象被连接成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象,我们把这些对象称为链中的节点。
实际开发中的例子
假设我们负责一个售卖手机的电商网站,经过分别交纳500 元定金和200 元定金的两轮预定后(订单已在此时生成),现在已经到了正式购买的阶段。公司针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过500 元定金的用户会收到100 元的商城优惠券,200 元定金的用户可以收到50 元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。我们的订单页面是PHP 吐出的模板,在页面加载之初,PHP 会传递给页面几个字段。
- 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('手机库存不足')
}
}
}
用职责链模式去思考,流程如下
// 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 ); // 输出:手机库存不足
其实吧,写成上面那样的代码逻辑已经很不错了,但是呢,仔细想一想,我们好像业务代码和判断逻辑揉一块了,但是如果又加了个300定金用户,我们的代码就显得扩展性没那么大了。下面我们继续修改
灵活可拆分的职责链节点
本节我们采用一种更灵活的方式,来改进上面的职责链模式,目标是让链中的各个节点可以灵活拆分和重组。首先需要改写一下分别表示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,表示在链中的下一个节点。
// Chain.prototype.setNextSuccessor 指定在链中的下一个节点
// Chain.prototype.passRequest 传递请求给某个节点
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);