1 職責鏈模式的定義
使多個物件都有機會處理請求,從而避免請求的傳送者和接收者的耦合關係。將這些物件連在一條鏈上,並沿這條鏈傳遞該請求,直到有一個物件處理它為止。我們把這些物件稱為鏈中的節點。
2 現實中的職責鏈
我們希望實現一個流程,商場預定手機:
- 預定定金500並已付款, 購買時列印
500 元定金預購, 得到 100 優惠券
- 預定定金200並已付款, 購買時列印
200 元定金預購, 得到 50 優惠券
- 否則是普通購買或者預定沒有付款:stock有庫存
普通購買, 無優惠券
,stock === 0時手機庫存不足
var order = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true) { // 500 元定金購買模式
console.log( '500 元定金預購, 得到 100 優惠券' );
} else if ( orderType === 2 && pay === true) { // 200 元定金購買模式
console.log( '200 元定金預購, 得到 50 優惠券' );
} else if ( orderType === 3 || pay === false) {
if ( stock > 0 ){
console.log( '普通購買, 無優惠券' );
} else {
console.log( '手機庫存不足' );
}
}
};
order( 1, true, 500); // 輸出: 500 元定金預購, 得到 100 優惠券
複製程式碼
4 使用職責鏈重構程式碼
- 分別表示 3 種購買模式的節點函式,我們約定,如果某個節點不能處理請求,則返回一個特定的字串 'nextSuccessor'來表示該請求需要繼續往後面傳遞。
- 把函式包裝進職責鏈節點,我們定義一個建構函式 Chain,在 new Chain 的時候傳遞的引數即為需要被包裝的函式, 同時它還擁有一個例項屬性 this.successor,表示在鏈中的下一個節點。
// 500預定
var order500 = function(orderType, pay, stock) {
if ( orderType === 1 && pay === true) { // 500 元定金購買模式
console.log( '500 元定金預購, 得到 100 優惠券' );
} else {
return 'nextSuccessor'; // // 我不知道下一個節點是誰,反正把請求往後面傳遞
}
}
// 200預定
var order200 = function(orderType, pay, stock) {
if ( orderType === 2 && pay === true) { // 500 元定金購買模式
console.log( '100 元定金預購, 得到 50 優惠券' );
} else {
return 'nextSuccessor'; // // 我不知道下一個節點是誰,反正把請求往後面傳遞
}
}
// 普通購買
var orderNormal = function(orderType, pay, stock) {
if ( stock > 0 ){
console.log( '普通購買,無優惠券' );
} else {
console.log( '手機庫存不足' );
}
}
// 定義作用域鏈
var Chain = function(fn) {
this.fn = fn;
this.nextSuccessor = null;
}
Chain.prototype.setNextSuccessor = function(fn) {
return this.nextSuccessor = fn;
};
Chain.prototype.passRequest = function() {
var ret = this.fn.apply(this, arguments);
if (ret === 'nextSuccessor') {
return this.nextSuccessor && this.nextSuccessor.passRequest.apply(this.nextSuccessor, arguments);
}
return ret;
}
複製程式碼
作用域鏈已經建立好了,下面就開始來呼叫:
var chain500 = new Chain(order500);
var chain200 = new Chain(order200);
var chainNormal = new Chain(orderNormal);
chain500.setNextSuccessor(chain200).setNextSuccessor(chainNormal);
chain500.passRequest(3, false, 20);
複製程式碼
通過改進,我們可以自由靈活地增加、移除和修改鏈中的節點順序,假如某天網站運營人員又想出了支援 300 元定金購買,那我們就在該鏈中增加一個節點即可.
var order200 = function(orderType, pay, stock) {
if ( orderType === 4 && pay === true) { // 300 元定金購買模式
console.log( '300 元定金預購, 得到 80 優惠券' );
} else {
return 'nextSuccessor'; // // 我不知道下一個節點是誰,反正把請求往後面傳遞
}
}
var chan300 = new Chian(order300);
chain500.setNextSuccessor(chan300);
chan300.setNextSuccessor(chain200);
複製程式碼
5 使用AOP實現職責鏈
在高階函式部分我們使用了AOP.我們改寫函式的Function.prototype.after函式,使其前一個函式返回nextSuccessor
時,我們繼續將請求傳遞給下一個函式。
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;
}
}
order500.after(order200).after(orderNormal)(2, true, 20);
複製程式碼
6 使用職責鏈獲取檔案上傳物件
var iEUpload = function() {
try{
return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上傳控制元件
} catch(e) {
return false;
}
}
var flashUpload = function() {
// if (supportFlash()) { // supportFlash 函式未提供
if (parseInt(Math.random() * 10) % 2 === 0) {
var str = '<object type="application/x-shockwave-flash"></object>';
return document.body.append(str);
}
return false;
}
var getFormUpload = function() {
var str = '<input name="file" type="file"/>'; // 表單上傳
return $( str ).appendTo( $('body') );
}
Function.prototype.after = function(fn) {
var self = this;
return function() {
var ret = self.apply(this, arguments);
if (!ret) {
return fn.apply(this, arguments);
}
return ret;
}
}
// 使用職責鏈呼叫
console.log(iEUpload.after(flashUpload).after(flashUpload)());
複製程式碼
小結
職責鏈運用好了,可以幫助我們很好管理程式碼,降低發起請求的物件和處理物件之間的耦合性。職責鏈中的節點數量和順序是自由變化的。無論在作用域鏈還是原型鏈,還是DOM節點的冒泡事件,都能夠看到職責鏈的影子。
- 首次使用職責鏈模式後,鏈中的節點可以自由組合,增加或刪除一個節點都是輕而易舉的(我覺得就是單向連結串列)
- 可以手動指定起始節點,不一定必須從第一個節點開始傳遞。
- 弊端:不能保證某個請求一定會被鏈中的節點處理
- 弊端:職責鏈模式會多出節點物件,可能在某一次的請求中大部分節點沒有起到實質性的作用。它們唯一的功能就是向下傳遞。從效能考慮,需要避免過長的職責鏈帶來的效能損耗。
使用策略模式完成相同的功能
我覺得使用策略模式顯得更簡潔。
function buy(stock) {
if (stock > 0) {
console.log( '普通購買, 無優惠券' );
} else {
console.log( '手機庫存不足' );
}
}
var stratege= {
1: function() {
console.log('500 元定金預購, 得到 100 優惠券');
},
5: function () {
console.log('500 元定金預購, 得到 50 優惠券')
}
};
// context 類
function order(type, pay, stock) {
if (stratege && pay) {
stratege[type]();
} else {
buy(stock)
}
}
order(1, true, 100)
複製程式碼