[TOC]
# 职责链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止,这些对象称为链中的节点。
## 实际开发中的职责链模式
假设:一个电商网站,对缴纳定金的用户有不同的优惠政策。已经支付过500元定金的用户会收到100元的优惠券,支付200元定金的用户可以收到50元的优惠券,没有支付定金的用户则为普通的购买模式。没有优惠券,且在库存有限的情况下不一定保证能买到。
```javascript
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){
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);
```
用职责链模式重构代码
```javascript
var order500 = function(orderType, pay, stock){
if(orderType === 1 && pay){
console.log('500元定金,得到100优惠券');
}else{
order200(orderType, pay, stock);
}
};
var order200 = function(orderType, pay, stock){
if(orderType === 2 && pay){
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);
order500(1, false, 500);
order500(2, true, 200);
order500(3, true, 0);
```
可以看到,请求在链条中的顺序非常僵硬,传递请求的代码被耦合在了业务函数之中:
```javascript
var order500 = function(orderType, pay, stock){
if(orderType === 1 && pay){
console.log('500元定金,得到100优惠券');
}else{
order200(orderType, pay, stock);
}
};
```
这是违反开放-封闭原则的。
## 灵活可拆分的职责链节点
进行一个约定,如果某个节点不能处理请求,则返回一个特定的字符串`nextSuccessor`来表示该请求需要继续往后面传递
```javascript
var order500 = function(orderType, pay, stock){
if(orderType === 1 && pay){
console.log('500元定金,得到100优惠券');
}else{
return 'nextSuccessor';
}
};
var order200 = function(orderType, pay, stock){
if(orderType === 2 && pay){
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;
};
// 封装成职责链节点
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);
chainOrder500.passRequest(2, true, 500);
chainOrder500.passRequest(3, true, 0);
```
## 异步的职责链
这时给`Chain`类增加一个原型方法`Chain.prototype.next`,表示手动传递请求给职责链中的下一个节点:
```javascript
Chain.prototype.next = function(){
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
};
var fn1 = new Chain(function(){
console.log('1');
return 'nextSuccessor';
});
var fn2 = new Chain(function(){
console.log(2);
var self = this;
setTimeout(function(){
self.next(); // 触发传递
}, 1000);
});
var fn3 = new Chain(function(){
console.log('3');
});
fn1.setNextSuccessor(fn2).setNextSuccessor(fn3);
fn1.passRequest();
```
## 职责链模式的优缺点
- 优点
1. 各自的处理函数互不影响;
2. 链中的节点对象可以灵活地拆分重组;
3. 可以手动设置起始节点;
- 缺点
1. 不能保证某个请求一定会被链中的某个节点处理,因此需要在链尾增加一个保底的接收者节点来处理这种即将离开链尾的请求;
2. 可能在某一次的请求传递过程中,大部分节点并没有起到实质性作用,它的作用仅仅是让请求传递下去,从性能方面考虑,我们要避免过长的职责链带来的性能损耗;
## 用AOP实现职责链
利用`JavaScript`的函数式特性,有一种更加方便的方法来创建职责链。
```javascript
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 = order500.after(order200).after(orderNormal);
order(1, true, 500);
order(2, true, 500);
order(3, true, 0);
```
用`AOP`来实现职责链既简单又巧妙,但这种把函数叠在一起的方式,同时也叠加了函数的作用域,如果链条太长的话,也会对性能有较大的影响。
实际上只要运用得当,职责链模式可以很好地帮助我们管理代码,降低发起请求的对象和处理请求的对象之间的耦合性。职责链中的节点数量和顺序是可以自由变化的。
职责链模式可以和组合模式结合在一起,用来连接部件和父部件,或是提高组合对象的效率。