《JavaScript設計模式與開發實踐》模式篇(10)—— 職責鏈模式

嗨呀豆豆呢發表於2018-12-19

職責鏈模式的定義是:使多個物件都有機會處理請求,從而避免請求的傳送者和接收者之間的耦合關係,將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它為止。職責鏈模式的名字非常形象,一系列可能會處理請求的物件被連線成一條鏈,請求在這些對 象之間依次傳遞,直到遇到一個可以處理它的物件,我們把這些物件稱為鏈中的節點

故事背景

假設我們負責一個售賣手機的電商網站,經過分別交納 500 元定金和 200 元定金的兩輪預定後(訂單已在此時生成),現在已經到了正式購買的階段。公司針對支付過定金的使用者有一定的優惠政策。在正式購買後,已經支付過 500 元定金的用 戶會收到 100 元的商城優惠券,200 元定金的使用者可以收到 50 元的優惠券,而之前沒有支付定金的使用者只能進入普通購買模式,也就是沒有優惠券,且在庫存有限的情況下不一定保證能買到。

程式碼實現(未使用職責鏈模式)

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 ){
// 200 元定金購買模式 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 優惠券複製程式碼

重構思路

現在我們採用職責鏈模式重構這段程式碼,先把 500 元訂單、200 元訂單以及普通購買分成 3 個函式。接下來把 orderType、pay、stock 這 3 個欄位當作引數傳遞給 500 元訂單函式,如果該函式不符合處理條件,則把這個請求傳遞給後面的 200 元訂單函式,如果 200 元訂單函式依然不能處理該請求,則繼續傳遞請求給普通購買函式。

程式碼重構(使用職責鏈模式)

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.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;

};
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);
複製程式碼

非同步的職責鏈

在現實開發中,我們經常會遇到一些非同步的問題,比如我們要在 節點函式中發起一個 ajax 非同步請求,非同步請求返回的結果才能決定是否繼續在職責鏈中 passRequest。這時候讓節點函式同步返回”nextSuccessor”已經沒有意義了,所以要給 Chain 類再增加一個原型方法 Chain.prototype.next,表示手動傳遞請求給職責鏈中的下一個節點

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();
複製程式碼

現在我們得到了一個特殊的鏈條,請求在鏈中的節點裡傳遞,但節點有權利決定什麼時候把 請求交給下一個節點。可以想象,非同步的職責鏈加上命令模式(把 ajax 請求封裝成命令物件),我們可以很方便地建立一個非同步 ajax 佇列庫。

用AOP實現職責鏈

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 );
// 輸出:普通購買,無優惠券複製程式碼

用 AOP 來實現職責鏈既簡單又巧妙,但這種把函式疊在一起的方式,同時也疊加了函式的 作用域,如果鏈條太長的話,也會對效能有較大的影響

小結

在 JavaScript 開發中,職責鏈模式是最容易被忽視的模式之一。實際上只要運用得當,職責鏈模式可以很好地幫助我們管理程式碼,降低發起請求的物件和處理請求的物件之間的耦合性。職責鏈中的節點數量和順序是可以自由變化的,我們可以在執行時決定鏈中包含哪些節點。

系列文章:

《JavaScript設計模式與開發實踐》最全知識點彙總大全

來源:https://juejin.im/post/5c1a37ca6fb9a049f8193cc8

相關文章