職責鏈模式的定義是:使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係,將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。
有這樣一個需求:
假設我們負責一個售賣手機的電商網站,經過分別交納500元定金和200元定金的兩輪預定後(訂單已在此時生成),現在已經到了正式購買的階段。
公司針對支付過定金的用戶有一定的優惠政策。在正式購買後,已經支付過500元定金的用戶會收到100元的商城優惠券,200元定金的用戶可以收到50元的優惠券,而之前沒有支付定金的用戶只能進入普通購買模式,也就是沒有優惠券,且在庫存有限的情況下不一定保證能買到。
這個需求寫成代碼就是這樣:
const 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優惠券
這樣的代碼有下面的這些問題:
- 大量重複代碼
- 所有邏輯都寫在了一個大函數裏面,維護起來很困難
- 耦合性太強,加入某天需求變更,比如新增一個300元的優惠策略,那麼幾乎要改動整塊代碼
使用職責鏈模式重寫
const order500 = function (orderType, pay, stock) {
if (orderType === 1 && pay === true) {
console.log('500元定金預購,得到100優惠券')
} else {
return 'nextSuccessor'
}
}
const order200 = function (orderType, pay, stock) {
if (orderType === 2 && pay === true) {
console.log('200元定金預購,得到50優惠券')
} else {
return 'nextSuccessor'
}
}
const orderNormal = function (orderType, pay, stock) {
if (stock > 0) {
console.log('普通購買,無優惠券')
} else {
console.log('手機庫存不足')
}
}
const Chain = function (fn) {
this.fn = fn
this.successor = null
}
Chain.prototype.setNextSuccessor = function (successor) {
return this.successor = successor
}
Chain.prototype.passRequest = function () {
let ret = this.fn.apply(this, arguments)
if (ret === 'nextSuccessor') {
return this.successor && this.successor.passRequest.apply(this.successor, arguments)
}
return ret
}
const chainOrder500 = new Chain(order500)
const chainOrder200 = new Chain(order200)
const 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(){
// 300價位訂單的處理邏輯
};
chainOrder300= new Chain( order300 );
chainOrder500.setNextSuccessor( chainOrder300);
chainOrder300.setNextSuccessor( chainOrder200);
利用js的高級函數,還能更加簡潔方便的創建職責鏈:
/* 用AOP實現職責鏈模式 */
Function.prototype.after=function(fn){
let self=this
return function(){
let ret=self.apply(this,arguments)
if(ret==='nextSuccessor'){
return fn.apply(this,arguments)
}
return ret
}
}
let order=order500.after(order200).after(orderNormal)
order( 1, true, 500 ); // 輸出:500元定金預購,得到100優惠券
order( 2, true, 500 ); // 輸出:200元定金預購,得到50優惠券
order( 1, false, 500 ); // 輸出:普通購買,無優惠券
用AOP來實現職責鏈既簡單又巧妙,但這種把函數疊在一起的方式,同時也疊加了函數的作用域,如果鏈條太長的話,也會對性能有較大的影響。
我的理解:職責鏈模式算是迭代器模式的一個延伸,裏面的思想與迭代器模式有些相似之處。迭代器是把隊列每個元素作爲輸入,然後用戶自己定義處理邏輯,而職責鏈是用戶指定多個處理邏輯,處理同一個輸入,只有滿足條件的那個處理邏輯才能真正輸出,而其他處理邏輯只是把輸入傳遞給下一個處理邏輯。