[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`來實現職責鏈既簡單又巧妙,但這種把函數疊在一起的方式,同時也疊加了函數的作用域,如果鏈條太長的話,也會對性能有較大的影響。
實際上只要運用得當,職責鏈模式可以很好地幫助我們管理代碼,降低發起請求的對象和處理請求的對象之間的耦合性。職責鏈中的節點數量和順序是可以自由變化的。
職責鏈模式可以和組合模式結合在一起,用來連接部件和父部件,或是提高組合對象的效率。