優點是:消除請求的發送者與接收者之間的耦合。
職責連是由多個不同的對象組成的,發送者是發送請求的對象,而接收者則是鏈中那些接收這種請求并且對其進行處理或傳遞的對象。請求本身有時候也可以是一個對象,它封裝了和操作有關的所有數據,基本實現流程如下:
1. 發送者知道鏈中的第一個接收者,它向這個接收者發送該請求。
2. 每一個接收者都對請求進行分析,然后要么處理它,要么它往下傳遞。
3. 每一個接收者知道其他的對象只有一個,即它在鏈中的下家(successor)。
4. 如果沒有任何接收者處理請求,那么請求會從鏈中離開。
我們可以理解職責鏈模式是處理請求組成的一條鏈,請求在這些對象之間依次傳遞,直到遇到一個可以處理它的對象,我們把這些對象稱為鏈中的節點。比如對象A給對象B發請求,如果B對象不處理,它就會把請求交給C,如果C對象不處理的話,它就會把請求交給D,依次類推,直到有一個對象能處理該請求為止,當然沒有任何對象處理該請求的話,那么請求就會從鏈中離開。
比如常見的一些外包公司接到一個項目,那么接到項目有可能是公司的負責項目的人或者經理級別的人,經理接到項目后自己不開發,直接把它交到項目經理來開發,項目經理自己肯定不樂意自己動手開發哦,它就把項目交給下面的碼農來做,所以碼農來處理它,如果碼農也不處理的話,那么這個項目可能會直接掛掉了,但是最后完成后,外包公司它并不知道這些項目中的那一部分具體有哪些人開發的,它并不知道,也并不關心的,它關心的是這個項目已交給外包公司已經開發完成了且沒有任何bug就可以了;所以職責鏈模式的優點就在這里:
消除請求的發送者(需要外包項目的公司)與接收者(外包公司)之間的耦合。
下面列舉個列子來說明職責鏈的好處:
天貓每年雙11都會做抽獎活動的,比如阿里巴巴想提高大家使用支付寶來支付的話,每一位用戶充值500元到支付寶的話,那么可以100%中獎100元紅包,
充值200元到支付寶的話,那么可以100%中獎20元的紅包,當然如果不充值的話,也可以抽獎,但是概率非常低,基本上是抽不到的,當然也有可能抽到的。
我們下面可以分析下代碼中的幾個字段值需要來判斷:
1. orderType(充值類型),如果值為1的話,說明是充值500元的用戶,如果為2的話,說明是充值200元的用戶,如果是3的話,說明是沒有充值的用戶。
2. isPay(是否已經成功充值了): 如果該值為true的話,說明已經成功充值了,否則的話 說明沒有充值成功;就當作普通用戶來購買。
3. count(表示數量);普通用戶抽獎,如果數量有的話,就可以拿到優惠卷,否則的話,不能拿到優惠卷。
~~~
// 我們一般寫代碼如下處理操作
var order = function(orderType,isPay,count) {
if(orderType == 1) { // 用戶充值500元到支付寶去
if(isPay == true) { // 如果充值成功的話,100%中獎
console.log("親愛的用戶,您中獎了100元紅包了");
}else {
// 充值失敗,就當作普通用戶來處理中獎信息
if(count > 0) {
console.log("親愛的用戶,您已抽到10元優惠卷");
}else {
console.log("親愛的用戶,請再接再厲哦");
}
}
}else if(orderType == 2) { // 用戶充值200元到支付寶去
if(isPay == true) { // 如果充值成功的話,100%中獎
console.log("親愛的用戶,您中獎了20元紅包了");
}else {
// 充值失敗,就當作普通用戶來處理中獎信息
if(count > 0) {
console.log("親愛的用戶,您已抽到10元優惠卷");
}else {
console.log("親愛的用戶,請再接再厲哦");
}
}
}else if(orderType == 3) {
// 普通用戶來處理中獎信息
if(count > 0) {
console.log("親愛的用戶,您已抽到10元優惠卷");
}else {
console.log("親愛的用戶,請再接再厲哦");
}
}
};
~~~
上面的代碼雖然可以實現需求,但是代碼不容易擴展且難以閱讀,假如以后我想一兩個條件,我想充值300元成功的話,可以中獎150元紅包,那么這時候又要改動里面的代碼,這樣業務邏輯與代碼耦合性相對比較高,一不小心就改錯了代碼;這時候我們試著使用職責鏈模式來依次傳遞對象來實現;
如下代碼:
~~~
function order500(orderType,isPay,count){
if(orderType == 1 && isPay == true) {
console.log("親愛的用戶,您中獎了100元紅包了");
}else {
// 自己不處理,傳遞給下一個對象order200去處理
order200(orderType,isPay,count);
}
};
function order200(orderType,isPay,count) {
if(orderType == 2 && isPay == true) {
console.log("親愛的用戶,您中獎了20元紅包了");
}else {
// 自己不處理,傳遞給下一個對象普通用戶去處理
orderNormal(orderType,isPay,count);
}
};
function orderNormal(orderType,isPay,count){
// 普通用戶來處理中獎信息
if(count > 0) {
console.log("親愛的用戶,您已抽到10元優惠卷");
}else {
console.log("親愛的用戶,請再接再厲哦");
}
}
~~~
如上代碼我們分別使用了三個函數order500,order200,orderNormal來分別處理自己的業務邏輯,如果目前的自己函數不能處理的事情,我們傳遞給下面的函數去處理,依次類推,直到有一個函數能處理他,否則的話,該職責鏈模式直接從鏈中離開,告訴不能處理,拋出錯誤提示,上面的代碼雖然可以當作職責鏈模式,但是我們看上面的代碼可以看到order500函數內依賴了order200這樣的函數,這樣就必須有這個函數,也違反了面向對象中的 開放-封閉原則。下面我們繼續來理解編寫 靈活可拆分的職責鏈節點。
~~~
function order500(orderType,isPay,count){
if(orderType == 1 && isPay == true) {
console.log("親愛的用戶,您中獎了100元紅包了");
}else {
//我不知道下一個節點是誰,反正把請求往后面傳遞
return "nextSuccessor";
}
};
function order200(orderType,isPay,count) {
if(orderType == 2 && isPay == true) {
console.log("親愛的用戶,您中獎了20元紅包了");
}else {
//我不知道下一個節點是誰,反正把請求往后面傳遞
return "nextSuccessor";
}
};
function orderNormal(orderType,isPay,count){
// 普通用戶來處理中獎信息
if(count > 0) {
console.log("親愛的用戶,您已抽到10元優惠卷");
}else {
console.log("親愛的用戶,請再接再厲哦");
}
}
// 下面需要編寫職責鏈模式的封裝構造函數方法
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;
}
//現在我們把3個函數分別包裝成職責鏈節點:
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); // 親愛的用戶,您中獎了100元紅包了
chainOrder500.passRequest(2,true,500); // 親愛的用戶,您中獎了20元紅包了
chainOrder500.passRequest(3,true,500); // 親愛的用戶,您已抽到10元優惠卷
chainOrder500.passRequest(1,false,0); // 親愛的用戶,請再接再厲哦
~~~
如上代碼;分別編寫order500,order200,orderNormal三個函數,在函數內分別處理自己的業務邏輯,如果自己的函數不能處理的話,就返回字符串nextSuccessor 往后面傳遞,然后封裝Chain這個構造函數,傳遞一個fn這個對象實列進來,且有自己的一個屬性successor,原型上有2個方法 setNextSuccessor 和 passRequest;setNextSuccessor 這個方法是指定節點在職責鏈中的順序的,把相對應的方法保存到this.successor這個屬性上,chainOrder500.setNextSuccessor(chainOrder200);chainOrder200.setNextSuccessor(chainOrderNormal);指定鏈中的順序,因此this.successor引用了order200這個方法和orderNormal這個方法,因此第一次chainOrder500.passRequest(1,true,500)調用的話,調用order500這個方法,直接輸出,第二次調用chainOrder500.passRequest(2,true,500);這個方法從鏈中首節點order500開始不符合,就返回successor字符串,然后this.successor && this.successor.passRequest.apply(this.successor,arguments);就執行這句代碼;上面我們說過this.successor這個屬性引用了2個方法 分別為order200和orderNormal,因此調用order200該方法,所以就返回了值,依次類推都是這個原理。那如果以后我們想充值300元的紅包的話,我們可以編寫order300這個函數,然后實列一下鏈chain包裝起來,指定一下職責鏈中的順序即可,里面的業務邏輯不需要做任何處理;
理解異步的職責鏈
上面的只是同步職責鏈,我們讓每個節點函數同步返回一個特定的值”nextSuccessor”,來表示是否把請求傳遞給下一個節點,在我們開發中會經常碰到ajax異步請求,請求成功后,需要做某某事情,那么這時候如果我們再套用上面的同步請求的話,就不生效了,下面我們來理解下使用異步的職責鏈來解決這個問題;我們給Chain類再增加一個原型方法Chain.prototype.next,表示手動傳遞請求給職責鏈中的一下個節點。
如下代碼:
~~~
function Fn1() {
console.log(1);
return "nextSuccessor";
}
function Fn2() {
console.log(2);
var self = this;
setTimeout(function(){
self.next();
},1000);
}
function Fn3() {
console.log(3);
}
// 下面需要編寫職責鏈模式的封裝構造函數方法
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;
}
Chain.prototype.next = function(){
return this.successor && this.successor.passRequest.apply(this.successor,arguments);
}
//現在我們把3個函數分別包裝成職責鏈節點:
var chainFn1 = new Chain(Fn1);
var chainFn2 = new Chain(Fn2);
var chainFn3 = new Chain(Fn3);
// 然后指定節點在職責鏈中的順序
chainFn1.setNextSuccessor(chainFn2);
chainFn2.setNextSuccessor(chainFn3);
chainFn1.passRequest(); // 打印出1,2 過1秒后 會打印出3
~~~
調用函數 chainFn1.passRequest();后,會先執行發送者Fn1這個函數 打印出1,然后返回字符串 nextSuccessor;
接著就執行return this.successor && this.successor.passRequest.apply(this.successor,arguments);這個函數到Fn2,打印2,接著里面有一個setTimeout定時器異步函數,需要把請求給職責鏈中的下一個節點,因此過一秒后會打印出3;
職責鏈模式的優點是:
1. 解耦了請求發送者和N個接收者之間的復雜關系,不需要知道鏈中那個節點能處理你的請求,所以你
只需要把請求傳遞到第一個節點即可。
2. 鏈中的節點對象可以靈活地拆分重組,增加或刪除一個節點,或者改變節點的位置都是很簡單的事情。
3. 我們還可以手動指定節點的起始位置,并不是說非得要從其實節點開始傳遞的.
缺點:職責鏈模式中多了一點節點對象,可能在某一次請求過程中,大部分節點沒有起到實質性作用,他們的作用只是讓
請求傳遞下去,從性能方面考慮,避免過長的職責鏈提高性能。