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

西木柚子發表於2019-03-01

設計模式系列 12– 職責鏈模式
image

專案做完了,作為老大的你決定叫上專案組的人一起聚個餐犒勞下大家,於是你去財務申請聚餐費用,財務告訴你聚餐費用申請的流程如下:

  • 小於500元,專案經理可以審批
  • 大於500小於1000,需要部門經理審批
  • 大於1000的都需要總經理審批

如果讓你來實現這個流程,該如何做呢?常規做法很簡單,如下所示:

#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(@"專案經理同意了費用申請");
}

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

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

}
@end複製程式碼

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

  • 申請費用的流程可能會經常變動。現在是專案經理–>部門經理—>總經理,如果哪天流程倒過來呢?或者加入了新的審批的人呢?上面的程式碼就無可避免的需要更改

  • 各個審批流程可能會更改。現在的三個審批流程如上所示,但是可能每個經理的審批的金額會有所更改,這個時候也需要更改上面的程式碼

抽象下上面的流程:客戶提出一個請求(申請聚餐費用),會有很多物件(經理)來處理這個請求,每個物件的處理邏輯是不同的,如果物件自己可以處理這個請求,就會處理完成然後把結果返回給客戶,如果自己不能處理,就交給其他物件處理。而且希望上面的流程可以靈活變動,處理請求的物件可以隨意組合替換,來適應新的業務需求。

要實現上述功能,可以使用職責鏈模式,下面就來具體看看


定義

使多個物件都有機會處理請求,從而避免請求的傳送者和接收者之間的耦合關係。將這 些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它為止。

分析下上面的業務場景,客戶端提取申請費用的請求,然後專案經理、部門經理、總經理依次處理這個請求,他們組成了一個請求處理鏈(專案經理–>部門經理—>總經理),客戶請求在這個鏈中傳遞,直到一個物件處理了這個請求才結束,否則一直向下傳遞到鏈的末尾,這正是職責鏈的功能。

要想讓流程的處理靈活多變,可以隨意組合和替換,就需要動態構建流程處理的步驟,每個步驟實現一個功能,然後把這些步驟串聯起來實現完整的請求處理鏈。

我們要實現傳送者和接受者解耦,那麼就需要讓提交幫助請求的物件不需要知道誰是最終提供幫助的物件。職責鏈模式給多個物件處理一個請求的機會,從而解耦傳送者和接受者。該請 求沿物件鏈傳遞直至其中一個物件處理它,從第一個物件開始,鏈中收到請求的物件要麼親自處理它,要麼轉發給鏈中的下一個候 選者。提交請求的物件並不明確地知道哪一個物件將會處理它,但是最終會有一個隱式物件來保證請求一定會被處理。

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

  • 有多個的物件可以處理一個請求,但是具體是哪個物件處理該請求則是在執行時刻才確定。
  • 想在不明確指定接收者的情況下,向多個物件中的一個提交請求。
  • 一個請求的處理鏈可以動態設定

UML結構圖及說明

設計模式系列 12– 職責鏈模式
image


程式碼實現

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、實現三個具體的職責鏈成員

#import "handler.h"

@interface projectManagerHandler : handler

@end

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

@implementation projectManagerHandler

-(void)handleRequest:(NSInteger)fee{
    if (fee < 500) {
        NSLog(@"專案經理同意了費用申請");
    }else{
        if (self.successor)
            NSLog(@"專案經理沒有許可權批准,轉到部門經理處理");
            [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(@"部門經理同意了費用申請");
    }else{
        if (self.successor)
            NSLog(@"部門經理沒有許可權批准,轉到總經理處理");
            [self.successor handleRequest:fee];
    }
}

@end複製程式碼
#import "handler.h"

@interface generalManagerHandler : handler

@end


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

#import "generalManagerHandler.h"

@implementation generalManagerHandler
-(void)handleRequest:(NSInteger)fee{
    NSLog(@"總經理同意了費用申請");
}
@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] 專案經理同意了費用申請
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] ----------------------------------
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] 專案經理沒有許可權批准,轉到部門經理處理
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] 部門經理同意了費用申請
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] ----------------------------------
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] 專案經理沒有許可權批准,轉到部門經理處理
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] 部門經理沒有許可權批准,轉到總經理處理
2016-12-14 19:48:37.406 責任鏈模式[65533:2201187] 總經理同意了費用申請
Program ended with exit code: 0複製程式碼

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

  • 費用小於1000,由專案經理審批
  • 費用大於1000,由總經理審批

流程裡面去掉了部門經理,並且每個經理的審批金額也變了,那麼如何更改呢?

很簡單,分為兩步

  1. 更改原來專案經理和總經理的審批流程
  2. 改變責任鏈,如下所示
  handler1.successor = handler2;
  handler2.successor = handler3;
改為:
  handler1.successor = handler3;複製程式碼

客戶端計算獎金的方式依然不變,這就是責任鏈的強大之處,可以動態選擇構成責任鏈的成員,看到這裡是不是覺得和裝飾者模式很類似?裝飾器模式也可以動態的選擇給原有物件新增各種功能,他們在某種程度上是可以互相替換的,兩者都可以實現動態給物件新增功能。

標準的職責鏈模式是鏈中有一個物件能處理請求,就停止把請求往下傳遞,如果不停止傳遞,那麼此時的職責鏈模式就和裝飾器模式很類似了,每個處理物件都相當於一個裝飾器。但是他們的目的值不同的,裝飾器是為了透明的給物件新增功能,而職責鏈是為了解耦請求的傳送者和接受者


優缺點

  1. 降低耦合度

    該模式使得一個物件無需知道是其他哪一個物件處理其請求。物件僅需
    知道該請求會被“正確”地處理。接收者和傳送者都沒有對方的明確的資訊,且鏈中的物件不需知道鏈的結構。
    結果是,職責鏈可簡化物件的相互連線。它們僅需保持一個指向其後繼者的引用,而不需保持它所有的候選接受者的引用。

  1. 動態組合職責

    當 在 對 象 中 分 派 職 責 時 , 職 責 鏈 給 你 更多的靈活性。你可以通過在執行時刻對該鏈進行動態的增加或修改,來增加或改變處理一個請求的職責鏈物件。

  1. 不保證被接受

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


處理多個請求的職責鏈

上面的職責鏈中的每個物件(各個經理)都只處理一種請求,實際情況每個物件可能需要處理多種業務型別。比如現在又增加了差旅費用申請。

常規做法是在抽象類handler中增加一個新的方法,然後在每個具體職責鏈物件中去實現這個方法,但是這樣做就違反了開閉原則,導致每次增加或者修改業務,都需要修改職責鏈中的每個物件。有沒有一種通用的處理辦法來處理所有的業務呢?肯定有,具體見demo,這裡就不展開說了。


功能鏈

其實在實際開發中一般都會對職責鏈進行變通使用,標準的職責鏈實現是:如果鏈中物件可以處理請求,就停止把請求傳遞到下一個物件。變通的處理就是不停止傳遞,每個物件都參與處理請求,這樣的例子很多。

比如各種web過濾器,一個連線請求進來,會進行許可權檢查,字符集轉換等過濾,可以被每個過濾功能都實現為一個職責鏈物件,然後對於不同的請求就可以動態組合這些職責鏈物件來構成一條職責鏈進行過濾。

再比如使用者的註冊流程一般如下圖所示:

設計模式系列 12– 職責鏈模式
image

藍色方塊代表的是一個個的流程,每個箭頭流向都是一條鏈,我們可以把每個流程時限為職責鏈物件,然後根據不同的流程組合出來不同的職責鏈來處理註冊。

這裡只是做拋磚引玉之用,具體程式碼就不演示了。職責鏈在實際開發過程中用途廣泛,只要是流程類的過程都可以嘗試使用職責鏈去實現。


Demo下載

職責鏈處理單個請求Demo

職責鏈處理多個請求Demo

相關文章