應項目需求,公司安排Jungle去成都出差一段時間。這不,Jungle剛結束出差生活,回到公司準備報銷。算了一下,Jungle一共有大概50萬的一筆小額票據需要報銷。按照公司規定,Jungle得先去找自己的組長簽字。
組長一看,“嘖嘖嘖,我只能處理10萬金額以下的報銷,你這單子我籤個字,你還得找兵哥(主管)簽字”,於是Jungle又跑去找兵哥。
兵哥看了,“嘖嘖嘖,我最多隻能管金額不超過30萬的單子,你得找春總(經理)簽字”。Jungle又不厭其煩地找到了春總。
春總一看,“呵,50萬,不少啊!但60萬以下我都能做主,這單子我給你簽了!要是超過60萬,你這狗子還得去找老闆!”Jungle總算鬆了一口氣,一級一級網上找領導,也是不容易呀!
在單位,每個領導都有不同的審批權限,不同額度的報銷單層層上報,最終到有權處理該金額範圍的領導手裏,該單子纔算審批完成。這是Jungle所在的公司(相信也是大部分公司)的制度。如果要用代碼來模擬這種制度,有沒有一種模式可以參考呢?
答案是:有!那就是職責鏈模式!
1.職責鏈模式簡介
職責鏈模式又叫責任鏈模式。很多情況下,可以處理某個請求的對象可能不止一個,請求可以沿着某一條對象之間形成的關係一級一級由下家傳遞到上家,形成一條鏈——職責鏈。職責鏈可以是直線,也可以是環或樹形結構。常見的職責鍊形式是直線。鏈上的每一個對象都是請求的處理者,客戶端要做的僅僅是發送請求,不需要關心請求的處理細節過程。由此,職責鏈模式將請求者和請求的接收者解耦。
職責鏈模式定義如下:
職責鏈模式:
避免將一個請求的發送者和接收者耦合在一起,讓多個對象都有機會處理請求。將接收請求的對象連接成一條鏈,並且沿着這條鏈傳遞請求,直到有一個對象能夠處理它爲止。
2.職責鏈模式結構
職責鏈模式的UML結構如下圖所示,職責鏈模式的核心在於引入了一個抽象處理者:
職責鏈模式中一共包含兩個角色:
- Handler(抽象處理者):抽象處理者一般爲抽象類,聲明瞭一個處理請求的接口handleRequest(),定義了一個抽象處理者類型的對象,作爲其對下家的引用,通過該引用可以形成一條責任鏈。
- ConcreteHandler(具體處理者): 是抽象處理者的子類,實現了處理請求的接口。在具體的實現中,如果該具體處理者能夠處理該請求,就處理它,否則將該請求轉發給後繼者。具體處理者可以訪問下一個對象。
由上述可知,在職責鏈模式中很多對象由每一個對象對其下家的引用連接起來形成一條鏈條,請求在這個鏈條上逐級傳遞,知道某一級能夠處理這個請求爲止。客戶端不知道也不必知道是哪一級處理者處理了該請求,因爲每個處理者都有相同的接口handleRequest()。接下來通過一個實例來進一步認識職責鏈模式。
3.職責鏈模式代碼實例
以引言中的例子爲例,對於不同金額的票據,公司不同級別的領導處理情況如下:
金額0~10萬:組長可處理
金額10~30萬:主管處理
金額30~60萬:經理處理
金額超過60萬:老闆處理
本節Jungle將用C++模擬該過程。該實例UML圖如下:
3.1.票據類
// 請求:票據
class Bill
{
public:
Bill(){}
Bill(int iId, string iName, double iAccount){
id = iId;
name = iName;
account = iAccount;
}
double getAccount(){
return this->account;
}
void print(){
printf("\nID:\t%d\n", id);
printf("Name:\t%s\n", name.c_str());
printf("Account:\t%f\n", account);
}
private:
int id;
string name;
double account;
};
3.2.抽象處理者
// 抽象處理者
class Approver
{
public:
Approver(){}
Approver(string iName){
setName(iName);
}
// 添加上級
void setSuperior(Approver *iSuperior){
this->superior = iSuperior;
}
// 處理請求
virtual void handleRequest(Bill*) = 0;
string getName(){
return name;
}
void setName(string iName){
name = iName;
}
protected:
Approver *superior;
private:
string name;
};
3.3.具體處理者
3.3.1.具體處理者:組長
// 具體處理者:組長
class GroupLeader :public Approver
{
public:
GroupLeader(){}
GroupLeader(string iName){
setName(iName);
}
// 處理請求
void handleRequest(Bill *bill){
if (bill->getAccount() < 10){
printf("組長 %s 處理了該票據,票據信息:",this->getName().c_str());
bill->print();
}
else{
printf("組長無權處理,轉交上級……\n");
this->superior->handleRequest(bill);
}
}
};
3.3.2.具體處理者:主管
// 具體處理者:主管
class Head :public Approver
{
public:
Head(){}
Head(string iName){
setName(iName);
}
// 處理請求
void handleRequest(Bill *bill){
if (bill->getAccount() >= 10 && bill->getAccount()<30){
printf("主管 %s 處理了該票據,票據信息:", this->getName().c_str());
bill->print();
}
else{
printf("主管無權處理,轉交上級……\n");
this->superior->handleRequest(bill);
}
}
};
3.3.3.具體處理者:經理
// 具體處理者:經理
class Manager :public Approver
{
public:
Manager(){}
Manager(string iName){
setName(iName);
}
// 處理請求
void handleRequest(Bill *bill){
if (bill->getAccount() >= 30 && bill->getAccount()<60){
printf("經理 %s 處理了該票據,票據信息:", this->getName().c_str());
bill->print();
}
else{
printf("經理無權處理,轉交上級……\n");
this->superior->handleRequest(bill);
}
}
};
3.3.4.具體處理者:老闆
// 具體處理者:老闆
class Boss :public Approver
{
public:
Boss(){}
Boss(string iName){
setName(iName);
}
// 處理請求
void handleRequest(Bill *bill){
printf("老闆 %s 處理了該票據,票據信息:", this->getName().c_str());
bill->print();
}
};
3.5.客戶端代碼示例
客戶端創建了四個角色,分別是組長、主管、經理和老闆,並設置了上下級關係。然後創建了4張票據,金額不等,都先統一交給組長處理。
#include <iostream>
#include "ChainOfResponsibility.h"
int main()
{
// 請求處理者:組長,兵哥,春總,老闆
Approver *zuzhang, *bingge, *chunzong, *laoban;
zuzhang = new GroupLeader("孫大哥");
bingge = new Head("兵哥");
chunzong = new Manager("春總");
laoban = new Boss("張老闆");
zuzhang->setSuperior(bingge);
bingge->setSuperior(chunzong);
chunzong->setSuperior(laoban);
// 創建報銷單
Bill *bill1 = new Bill(1, "Jungle", 8);
Bill *bill2 = new Bill(2, "Lucy", 14.4);
Bill *bill3 = new Bill(3, "Jack", 32.9);
Bill *bill4 = new Bill(4, "Tom", 89);
// 全部先交給組長審批
zuzhang->handleRequest(bill1); printf("\n");
zuzhang->handleRequest(bill2); printf("\n");
zuzhang->handleRequest(bill3); printf("\n");
zuzhang->handleRequest(bill4);
printf("\n\n");
system("pause");
return 0;
}
3.6.效果
運行結果如下圖,可以看到,針對不同金額的票據,處理請求在不同職級之間層層上報,成功模擬了引言中的過程。
4.總結
優點:
- 將請求的接收者和處理者解耦,客戶端無需知道具體處理者,只針對抽象處理者編程,簡化了客戶端編程過程,降低系統耦合度;
- 在系統中增加一個新的處理者時,只需要繼承抽象處理者,重新實現handleRequest()接口,無需改動原有代碼,符合開閉原則;
- 給對象分配職責時,職責鏈模式賦予系統更多靈活性。
缺點:
- 請求沒有一個明確的接收者,有可能遇到請求無法響應的問題;
- 比較長的職責鏈,其處理過程會很長。
- 建立職責鏈的工作是在客戶端進行,如果建立不當,可能導致循環調用或者調用失敗。
適用環境:
- 有多個對象處理同一個請求,具體由誰來處理是在運行時決定,客戶端只需發出請求到職責鏈上,而無需關心具體是誰來處理;
- 可動態指定一組對象處理請求,客戶端可以動態創建職責鏈來處理請求,還可以改變職責鏈中各個處理者之間的上下級關係。
歡迎關注知乎專欄:Jungle是一個用Qt的工業Robot
歡迎關注Jungle的微信公衆號:Jungle筆記(後臺回覆“資料”,領取41本計算機領域經典書籍)