設(shè)計(jì)模式系列12--職責(zé)鏈模式

image

項(xiàng)目做完了,作為老大的你決定叫上項(xiàng)目組的人一起聚個(gè)餐犒勞下大家,于是你去財(cái)務(wù)申請(qǐng)聚餐費(fèi)用,財(cái)務(wù)告訴你聚餐費(fèi)用申請(qǐng)的流程如下:

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

如果讓你來實(shí)現(xiàn)這個(gè)流程,該如何做呢?常規(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(@"項(xiàng)目經(jīng)理同意了費(fèi)用申請(qǐng)");
}

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

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

}
@end

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

  • 申請(qǐng)費(fèi)用的流程可能會(huì)經(jīng)常變動(dòng)。現(xiàn)在是項(xiàng)目經(jīng)理-->部門經(jīng)理--->總經(jīng)理,如果哪天流程倒過來呢?或者加入了新的審批的人呢?上面的代碼就無可避免的需要更改

  • 各個(gè)審批流程可能會(huì)更改。現(xiàn)在的三個(gè)審批流程如上所示,但是可能每個(gè)經(jīng)理的審批的金額會(huì)有所更改,這個(gè)時(shí)候也需要更改上面的代碼

抽象下上面的流程:客戶提出一個(gè)請(qǐng)求(申請(qǐng)聚餐費(fèi)用),會(huì)有很多對(duì)象(經(jīng)理)來處理這個(gè)請(qǐng)求,每個(gè)對(duì)象的處理邏輯是不同的,如果對(duì)象自己可以處理這個(gè)請(qǐng)求,就會(huì)處理完成然后把結(jié)果返回給客戶,如果自己不能處理,就交給其他對(duì)象處理。而且希望上面的流程可以靈活變動(dòng),處理請(qǐng)求的對(duì)象可以隨意組合替換,來適應(yīng)新的業(yè)務(wù)需求。

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


定義

使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免請(qǐng)求的發(fā)送者和接收者之間的耦合關(guān)系。將這 些對(duì)象連成一條鏈,并沿著這條鏈傳遞該請(qǐng)求,直到有一個(gè)對(duì)象處理它為止。

分析下上面的業(yè)務(wù)場景,客戶端提取申請(qǐng)費(fèi)用的請(qǐng)求,然后項(xiàng)目經(jīng)理、部門經(jīng)理、總經(jīng)理依次處理這個(gè)請(qǐng)求,他們組成了一個(gè)請(qǐng)求處理鏈(項(xiàng)目經(jīng)理-->部門經(jīng)理--->總經(jīng)理),客戶請(qǐng)求在這個(gè)鏈中傳遞,直到一個(gè)對(duì)象處理了這個(gè)請(qǐng)求才結(jié)束,否則一直向下傳遞到鏈的末尾,這正是職責(zé)鏈的功能。

要想讓流程的處理靈活多變,可以隨意組合和替換,就需要?jiǎng)討B(tài)構(gòu)建流程處理的步驟,每個(gè)步驟實(shí)現(xiàn)一個(gè)功能,然后把這些步驟串聯(lián)起來實(shí)現(xiàn)完整的請(qǐng)求處理鏈。

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

由上述分析可知,職責(zé)鏈模式適用于如下幾種情況:

  • 有多個(gè)的對(duì)象可以處理一個(gè)請(qǐng)求,但是具體是哪個(gè)對(duì)象處理該請(qǐng)求則是在運(yùn)行時(shí)刻才確定。
  • 想在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交請(qǐng)求。
  • 一個(gè)請(qǐng)求的處理鏈可以動(dòng)態(tài)設(shè)置

UML結(jié)構(gòu)圖及說明

image

代碼實(shí)現(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、實(shí)現(xiàn)三個(gè)具體的職責(zé)鏈成員

#import "handler.h"

@interface projectManagerHandler : handler

@end

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

@implementation projectManagerHandler

-(void)handleRequest:(NSInteger)fee{
    if (fee < 500) {
        NSLog(@"項(xiàng)目經(jīng)理同意了費(fèi)用申請(qǐng)");
    }else{
        if (self.successor)
            NSLog(@"項(xiàng)目經(jīng)理沒有權(quán)限批準(zhǔn),轉(zhuǎn)到部門經(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)理同意了費(fèi)用申請(qǐng)");
    }else{
        if (self.successor)
            NSLog(@"部門經(jīng)理沒有權(quán)限批準(zhǔn),轉(zhuǎn)到總經(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)理同意了費(fèi)用申請(qǐng)");
}
@end

3、測試

     handler *handler1 = [projectManagerHandler new];
        handler *handler2 = [depManagerHandler new];
        handler *handler3 = [generalManagerHandler new];
        
        //設(shè)置責(zé)任鏈中的下一個(gè)處理對(duì)象
        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 責(zé)任鏈模式[65533:2201187] 項(xiàng)目經(jīng)理同意了費(fèi)用申請(qǐng)
2016-12-14 19:48:37.406 責(zé)任鏈模式[65533:2201187] ----------------------------------
2016-12-14 19:48:37.406 責(zé)任鏈模式[65533:2201187] 項(xiàng)目經(jīng)理沒有權(quán)限批準(zhǔn),轉(zhuǎn)到部門經(jīng)理處理
2016-12-14 19:48:37.406 責(zé)任鏈模式[65533:2201187] 部門經(jīng)理同意了費(fèi)用申請(qǐng)
2016-12-14 19:48:37.406 責(zé)任鏈模式[65533:2201187] ----------------------------------
2016-12-14 19:48:37.406 責(zé)任鏈模式[65533:2201187] 項(xiàng)目經(jīng)理沒有權(quán)限批準(zhǔn),轉(zhuǎn)到部門經(jīng)理處理
2016-12-14 19:48:37.406 責(zé)任鏈模式[65533:2201187] 部門經(jīng)理沒有權(quán)限批準(zhǔn),轉(zhuǎn)到總經(jīng)理處理
2016-12-14 19:48:37.406 責(zé)任鏈模式[65533:2201187] 總經(jīng)理同意了費(fèi)用申請(qǐng)
Program ended with exit code: 0

假設(shè)這個(gè)時(shí)候申請(qǐng)流程變了,如下所示:

  • 費(fèi)用小于1000,由項(xiàng)目經(jīng)理審批
  • 費(fèi)用大于1000,由總經(jīng)理審批

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

很簡單,分為兩步

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

客戶端計(jì)算獎(jiǎng)金的方式依然不變,這就是責(zé)任鏈的強(qiáng)大之處,可以動(dòng)態(tài)選擇構(gòu)成責(zé)任鏈的成員,看到這里是不是覺得和裝飾者模式很類似?裝飾器模式也可以動(dòng)態(tài)的選擇給原有對(duì)象添加各種功能,他們?cè)谀撤N程度上是可以互相替換的,兩者都可以實(shí)現(xiàn)動(dòng)態(tài)給對(duì)象添加功能。

標(biāo)準(zhǔn)的職責(zé)鏈模式是鏈中有一個(gè)對(duì)象能處理請(qǐng)求,就停止把請(qǐng)求往下傳遞,如果不停止傳遞,那么此時(shí)的職責(zé)鏈模式就和裝飾器模式很類似了,每個(gè)處理對(duì)象都相當(dāng)于一個(gè)裝飾器。但是他們的目的值不同的,裝飾器是為了透明的給對(duì)象添加功能,而職責(zé)鏈?zhǔn)菫榱私怦钫?qǐng)求的發(fā)送者和接受者


優(yōu)缺點(diǎn)

  1. 降低耦合度

    該模式使得一個(gè)對(duì)象無需知道是其他哪一個(gè)對(duì)象處理其請(qǐng)求。對(duì)象僅需
    知道該請(qǐng)求會(huì)被“正確”地處理。接收者和發(fā)送者都沒有對(duì)方的明確的信息,且鏈中的對(duì)象不需知道鏈的結(jié)構(gòu)。
    結(jié)果是,職責(zé)鏈可簡化對(duì)象的相互連接。它們僅需保持一個(gè)指向其后繼者的引用,而不需保持它所有的候選接受者的引用。

  1. 動(dòng)態(tài)組合職責(zé)

    當(dāng) 在 對(duì) 象 中 分 派 職 責(zé) 時(shí) , 職 責(zé) 鏈 給 你 更多的靈活性。你可以通過在運(yùn)行時(shí)刻對(duì)該鏈進(jìn)行動(dòng)態(tài)的增加或修改,來增加或改變處理一個(gè)請(qǐng)求的職責(zé)鏈對(duì)象。

  1. 不保證被接受

    既然一個(gè)請(qǐng)求沒有明確的接收者,那么就不能保證它一定會(huì)被處理。 該請(qǐng)求可能一直到鏈的末端都得不到處理。一個(gè)請(qǐng)求也可能因該鏈沒有被正確配置而得不到處理。


處理多個(gè)請(qǐng)求的職責(zé)鏈

上面的職責(zé)鏈中的每個(gè)對(duì)象(各個(gè)經(jīng)理)都只處理一種請(qǐng)求,實(shí)際情況每個(gè)對(duì)象可能需要處理多種業(yè)務(wù)類型。比如現(xiàn)在又增加了差旅費(fèi)用申請(qǐng)。

常規(guī)做法是在抽象類handler中增加一個(gè)新的方法,然后在每個(gè)具體職責(zé)鏈對(duì)象中去實(shí)現(xiàn)這個(gè)方法,但是這樣做就違反了開閉原則,導(dǎo)致每次增加或者修改業(yè)務(wù),都需要修改職責(zé)鏈中的每個(gè)對(duì)象。有沒有一種通用的處理辦法來處理所有的業(yè)務(wù)呢?肯定有,具體見demo,這里就不展開說了。


功能鏈

其實(shí)在實(shí)際開發(fā)中一般都會(huì)對(duì)職責(zé)鏈進(jìn)行變通使用,標(biāo)準(zhǔn)的職責(zé)鏈實(shí)現(xiàn)是:如果鏈中對(duì)象可以處理請(qǐng)求,就停止把請(qǐng)求傳遞到下一個(gè)對(duì)象。變通的處理就是不停止傳遞,每個(gè)對(duì)象都參與處理請(qǐng)求,這樣的例子很多。

比如各種web過濾器,一個(gè)連接請(qǐng)求進(jìn)來,會(huì)進(jìn)行權(quán)限檢查,字符集轉(zhuǎn)換等過濾,可以被每個(gè)過濾功能都實(shí)現(xiàn)為一個(gè)職責(zé)鏈對(duì)象,然后對(duì)于不同的請(qǐng)求就可以動(dòng)態(tài)組合這些職責(zé)鏈對(duì)象來構(gòu)成一條職責(zé)鏈進(jìn)行過濾。

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

image

藍(lán)色方塊代表的是一個(gè)個(gè)的流程,每個(gè)箭頭流向都是一條鏈,我們可以把每個(gè)流程時(shí)限為職責(zé)鏈對(duì)象,然后根據(jù)不同的流程組合出來不同的職責(zé)鏈來處理注冊(cè)。

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


Demo下載

職責(zé)鏈處理單個(gè)請(qǐng)求Demo

職責(zé)鏈處理多個(gè)請(qǐng)求Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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