設計模式系列12--職責鏈模式

image

項目做完了食寡,作為老大的你決定叫上項目組的人一起聚個餐犒勞下大家拟烫,于是你去財務申請聚餐費用,財務告訴你聚餐費用申請的流程如下:

  • 小于500元赠摇,項目經(jīng)理可以審批
  • 大于500小于1000超燃,需要部門經(jīng)理審批
  • 大于1000的都需要總經(jīng)理審批

如果讓你來實現(xiàn)這個流程区拳,該如何做呢?常規(guī)做法很簡單意乓,如下所示:

#import "feeRequest.h"

@implementation feeRequest

-(void)applayForFee:(NSInteger)fee{
    if (fee < 500){
        [self projectHandle];
    
    }else if(fee < 1000 && fee > 500){
        [self depManagerHandle];
    
    }else{
        [self generalManagerHandle];
    }
}

-(void)projectHandle{
    NSLog(@"項目經(jīng)理同意了費用申請");
}

-(void)depManagerHandle{
    NSLog(@"部門經(jīng)理同意了費用申請");
}

-(void)generalManagerHandle{
    NSLog(@"總經(jīng)理同意了費用申請");

}
@end

實現(xiàn)起來很簡單吧樱调。但是仔細分析下,上面的寫法有如下兩個問題

  • 申請費用的流程可能會經(jīng)常變動∏⑺玻現(xiàn)在是項目經(jīng)理-->部門經(jīng)理--->總經(jīng)理本涕,如果哪天流程倒過來呢?或者加入了新的審批的人呢伙窃?上面的代碼就無可避免的需要更改

  • 各個審批流程可能會更改∑杏保現(xiàn)在的三個審批流程如上所示,但是可能每個經(jīng)理的審批的金額會有所更改为障,這個時候也需要更改上面的代碼

抽象下上面的流程:客戶提出一個請求(申請聚餐費用)晦闰,會有很多對象(經(jīng)理)來處理這個請求,每個對象的處理邏輯是不同的鳍怨,如果對象自己可以處理這個請求呻右,就會處理完成然后把結果返回給客戶,如果自己不能處理鞋喇,就交給其他對象處理声滥。而且希望上面的流程可以靈活變動,處理請求的對象可以隨意組合替換,來適應新的業(yè)務需求落塑。

要實現(xiàn)上述功能纽疟,可以使用職責鏈模式,下面就來具體看看


定義

使多個對象都有機會處理請求憾赁,從而避免請求的發(fā)送者和接收者之間的耦合關系污朽。將這 些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它為止龙考。

分析下上面的業(yè)務場景蟆肆,客戶端提取申請費用的請求,然后項目經(jīng)理晦款、部門經(jīng)理炎功、總經(jīng)理依次處理這個請求,他們組成了一個請求處理鏈(項目經(jīng)理-->部門經(jīng)理--->總經(jīng)理)缓溅,客戶請求在這個鏈中傳遞亡问,直到一個對象處理了這個請求才結束,否則一直向下傳遞到鏈的末尾肛宋,這正是職責鏈的功能。

要想讓流程的處理靈活多變束世,可以隨意組合和替換酝陈,就需要動態(tài)構建流程處理的步驟,每個步驟實現(xiàn)一個功能毁涉,然后把這些步驟串聯(lián)起來實現(xiàn)完整的請求處理鏈沉帮。

我們要實現(xiàn)發(fā)送者和接受者解耦,那么就需要讓提交幫助請求的對象不需要知道誰是最終提供幫助的對象贫堰。職責鏈模式給多個對象處理一個請求的機會,從而解耦發(fā)送者和接受者穆壕。該請 求沿對象鏈傳遞直至其中一個對象處理它,從第一個對象開始,鏈中收到請求的對象要么親自處理它,要么轉發(fā)給鏈中的下一個候 選者其屏。提交請求的對象并不明確地知道哪一個對象將會處理它喇勋,但是最終會有一個隱式對象來保證請求一定會被處理。

由上述分析可知偎行,職責鏈模式適用于如下幾種情況:

  • 有多個的對象可以處理一個請求川背,但是具體是哪個對象處理該請求則是在運行時刻才確定。
  • 想在不明確指定接收者的情況下蛤袒,向多個對象中的一個提交請求熄云。
  • 一個請求的處理鏈可以動態(tài)設置

UML結構圖及說明

image

代碼實現(xiàn)

1、定義抽象類handler

#import <Foundation/Foundation.h>

@interface handler : NSObject
@property(strong,nonatomic)handler *successor;

-(void)handleRequest:(NSInteger)fee;
@end

==================

#import "handler.h"

@implementation handler


-(void)handleRequest:(NSInteger)fee{
    
}

@end

2妙真、實現(xiàn)三個具體的職責鏈成員

#import "handler.h"

@interface projectManagerHandler : handler

@end

=============================
#import "projectManagerHandler.h"

@implementation projectManagerHandler

-(void)handleRequest:(NSInteger)fee{
    if (fee < 500) {
        NSLog(@"項目經(jīng)理同意了費用申請");
    }else{
        if (self.successor)
            NSLog(@"項目經(jīng)理沒有權限批準缴允,轉到部門經(jīng)理處理");
            [self.successor handleRequest:fee];
    }
}
@end


#import "handler.h"

@interface depManagerHandler : handler

@end

=====================
#import "depManagerHandler.h"

@implementation depManagerHandler

-(void)handleRequest:(NSInteger)fee{
    if (fee > 500 && fee < 1000) {
        NSLog(@"部門經(jīng)理同意了費用申請");
    }else{
        if (self.successor)
            NSLog(@"部門經(jīng)理沒有權限批準,轉到總經(jīng)理處理");
            [self.successor handleRequest:fee];
    }
}

@end


#import "handler.h"

@interface generalManagerHandler : handler

@end


=====================

#import "generalManagerHandler.h"

@implementation generalManagerHandler
-(void)handleRequest:(NSInteger)fee{
    NSLog(@"總經(jīng)理同意了費用申請");
}
@end

3珍德、測試

     handler *handler1 = [projectManagerHandler new];
        handler *handler2 = [depManagerHandler new];
        handler *handler3 = [generalManagerHandler new];
        
        //設置責任鏈中的下一個處理對象
        handler1.successor = handler2;
        handler2.successor = handler3;
        
        [handler1 handleRequest:100];
        
        NSLog(@"----------------------------------");
        [handler1 handleRequest:700];
        
        NSLog(@"----------------------------------");
        [handler1 handleRequest:10000];

4练般、輸出

2016-12-14 19:48:37.405 責任鏈模式[65533:2201187] 項目經(jīng)理同意了費用申請
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] ----------------------------------
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] 項目經(jīng)理沒有權限批準矗漾,轉到部門經(jīng)理處理
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] 部門經(jīng)理同意了費用申請
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] ----------------------------------
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] 項目經(jīng)理沒有權限批準,轉到部門經(jīng)理處理
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] 部門經(jīng)理沒有權限批準踢俄,轉到總經(jīng)理處理
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] 總經(jīng)理同意了費用申請
Program ended with exit code: 0

假設這個時候申請流程變了缩功,如下所示:

  • 費用小于1000,由項目經(jīng)理審批
  • 費用大于1000都办,由總經(jīng)理審批

流程里面去掉了部門經(jīng)理嫡锌,并且每個經(jīng)理的審批金額也變了,那么如何更改呢琳钉?

很簡單势木,分為兩步

  1. 更改原來項目經(jīng)理和總經(jīng)理的審批流程
  2. 改變責任鏈,如下所示
  handler1.successor = handler2;
  handler2.successor = handler3;
改為:
  handler1.successor = handler3;

客戶端計算獎金的方式依然不變歌懒,這就是責任鏈的強大之處啦桌,可以動態(tài)選擇構成責任鏈的成員,看到這里是不是覺得和裝飾者模式很類似及皂?裝飾器模式也可以動態(tài)的選擇給原有對象添加各種功能甫男,他們在某種程度上是可以互相替換的,兩者都可以實現(xiàn)動態(tài)給對象添加功能验烧。

標準的職責鏈模式是鏈中有一個對象能處理請求板驳,就停止把請求往下傳遞,如果不停止傳遞碍拆,那么此時的職責鏈模式就和裝飾器模式很類似了若治,每個處理對象都相當于一個裝飾器。但是他們的目的值不同的感混,裝飾器是為了透明的給對象添加功能端幼,而職責鏈是為了解耦請求的發(fā)送者和接受者


優(yōu)缺點

  1. 降低耦合度

    該模式使得一個對象無需知道是其他哪一個對象處理其請求。對象僅需
    知道該請求會被“正確”地處理弧满。接收者和發(fā)送者都沒有對方的明確的信息婆跑,且鏈中的對象不需知道鏈的結構。
    結果是庭呜,職責鏈可簡化對象的相互連接洽蛀。它們僅需保持一個指向其后繼者的引用,而不需保持它所有的候選接受者的引用。

  1. 動態(tài)組合職責

    當 在 對 象 中 分 派 職 責 時 , 職 責 鏈 給 你 更多的靈活性疟赊。你可以通過在運行時刻對該鏈進行動態(tài)的增加或修改郊供,來增加或改變處理一個請求的職責鏈對象。

  1. 不保證被接受

    既然一個請求沒有明確的接收者,那么就不能保證它一定會被處理近哟。 該請求可能一直到鏈的末端都得不到處理驮审。一個請求也可能因該鏈沒有被正確配置而得不到處理。


處理多個請求的職責鏈

上面的職責鏈中的每個對象(各個經(jīng)理)都只處理一種請求,實際情況每個對象可能需要處理多種業(yè)務類型疯淫。比如現(xiàn)在又增加了差旅費用申請地来。

常規(guī)做法是在抽象類handler中增加一個新的方法,然后在每個具體職責鏈對象中去實現(xiàn)這個方法熙掺,但是這樣做就違反了開閉原則未斑,導致每次增加或者修改業(yè)務,都需要修改職責鏈中的每個對象币绩。有沒有一種通用的處理辦法來處理所有的業(yè)務呢蜡秽?肯定有,具體見demo缆镣,這里就不展開說了芽突。


功能鏈

其實在實際開發(fā)中一般都會對職責鏈進行變通使用,標準的職責鏈實現(xiàn)是:如果鏈中對象可以處理請求董瞻,就停止把請求傳遞到下一個對象寞蚌。變通的處理就是不停止傳遞,每個對象都參與處理請求钠糊,這樣的例子很多挟秤。

比如各種web過濾器,一個連接請求進來抄伍,會進行權限檢查煞聪,字符集轉換等過濾,可以被每個過濾功能都實現(xiàn)為一個職責鏈對象逝慧,然后對于不同的請求就可以動態(tài)組合這些職責鏈對象來構成一條職責鏈進行過濾。

再比如用戶的注冊流程一般如下圖所示:

image

藍色方塊代表的是一個個的流程啄糙,每個箭頭流向都是一條鏈笛臣,我們可以把每個流程時限為職責鏈對象,然后根據(jù)不同的流程組合出來不同的職責鏈來處理注冊隧饼。

這里只是做拋磚引玉之用沈堡,具體代碼就不演示了。職責鏈在實際開發(fā)過程中用途廣泛燕雁,只要是流程類的過程都可以嘗試使用職責鏈去實現(xiàn)诞丽。


Demo下載

職責鏈處理單個請求Demo

職責鏈處理多個請求Demo

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拐格,隨后出現(xiàn)的幾起案子僧免,更是在濱河造成了極大的恐慌,老刑警劉巖捏浊,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懂衩,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機浊洞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門牵敷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人法希,你說我怎么就攤上這事枷餐。” “怎么了苫亦?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵毛肋,是天一觀的道長。 經(jīng)常有香客問我著觉,道長村生,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任饼丘,我火速辦了婚禮趁桃,結果婚禮上,老公的妹妹穿的比我還像新娘肄鸽。我一直安慰自己卫病,他們只是感情好,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布典徘。 她就那樣靜靜地躺著蟀苛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逮诲。 梳的紋絲不亂的頭發(fā)上帜平,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音梅鹦,去河邊找鬼裆甩。 笑死,一個胖子當著我的面吹牛齐唆,可吹牛的內(nèi)容都是我干的嗤栓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼箍邮,長吁一口氣:“原來是場噩夢啊……” “哼茉帅!你這毒婦竟也來了?” 一聲冷哼從身側響起锭弊,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤堪澎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后味滞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體全封,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡马昙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了刹悴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片行楞。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖土匀,靈堂內(nèi)的尸體忽然破棺而出子房,到底是詐尸還是另有隱情,我是刑警寧澤就轧,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布证杭,位于F島的核電站,受9級特大地震影響妒御,放射性物質發(fā)生泄漏解愤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一乎莉、第九天 我趴在偏房一處隱蔽的房頂上張望送讲。 院中可真熱鬧,春花似錦惋啃、人聲如沸哼鬓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽异希。三九已至,卻和暖如春绒瘦,著一層夾襖步出監(jiān)牢的瞬間称簿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工惰帽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留憨降,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓善茎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親频轿。 傳聞我的和親對象是個殘疾皇子垂涯,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容