權力越大職責越大——C#中的職責鏈模式
阿新 • • 發佈:2020-07-09
大家好,歡迎來到老胡的部落格,今天我們繼續瞭解設計模式中的職責鏈模式,這是一個比較簡單的模式。跟往常一樣,我們還是從一個真實世界的例子入手,這樣大家也對這個模式的應用場景有更深刻的理解。
#### 一個真實的栗子
作為上班族,相信大家對請假都不陌生,每個公司都有自己請假的流程,稍微講究點的公司還會有細緻的規定,比如,3天以內的假期,小組長有權力批准,3天以上的假期就要找更高級別的領導批准。這種制度就是典型的權力越大職責越大——畢竟,批長假的職責只在高階主管那裡存在。
除了規定出這樣細緻的要求之外,大部分公司還有用軟體實現了請假流程,當請假人員提出請假申請的時候,會依據請假天數,轉發給具有許可權的人員審批,讓我們看看這個系統的程式碼實現吧。
##### 請假系統實現
在這個系統中,我們假定:
- 小組長可以審批3天以內的請假請求
- 部門經理可以審批5天以內的請假請求
- 10天以內的請假請求只有老闆才能審批
- 我們同時假定,這個公司的管理層非常人性化,請假都能得到批准,除非大於10天,因為這種情況沒人可以審批
###### 請假申請
這是最簡單的類,封裝了請假天數和請假申請人
```csharp
class VacationRequest
{
public int DayNum { get; set; }
public string RequesterName { get; set; }
}
```
###### 假期審批者
首先建立一個抽象類,假期審批者,封裝假期審批的基本邏輯,即,如果當前人員有許可權審批當前假期申請,就處理
```csharp
abstract class VacationApprover
{
protected VacationApprover(int dayCanHandle)
{
DayCanHandle = dayCanHandle;
}
public int DayCanHandle { get; protected set; }
public void HandleVacationRequest(VacationRequest request)
{
if (request.DayNum <= DayCanHandle)
{
DoHandleVacationRequest(request);
}
}
protected abstract void DoHandleVacationRequest(VacationRequest request);
}
```
當然,抽象類只需要確定演算法骨架,限定只有當前人員能處理這個請求的時候,才進行審批工作,至於具體的審批實現,留給子類自己去覆蓋,這種在父類固定演算法骨架,暴露部分覆蓋點給子類的做法,就是之前我們提到過的[TemplateMethod模式](https://www.cnblogs.com/deatharthas/p/13041420.html)
###### 具體假期審批者
小組長,部門經理,老闆,都在這裡建立,他們分別處理能審批3、5、10天的請假申請
```csharp
class TeamLeader : VacationApprover
{
private const int DAY_CAN_HANDLE_TEAMLEADER = 3;
public TeamLeader() : base(DAY_CAN_HANDLE_TEAMLEADER) { }
protected override void DoHandleVacationRequest(VacationRequest request)
{
Console.WriteLine("Now team leader handle this request");
Console.WriteLine("Team leader accept this request");
}
}
class DepartmentLeader : VacationApprover
{
private const int DAY_CAN_HANDLE_DEPARTMENTLEADER = 5;
public DepartmentLeader() : base(DAY_CAN_HANDLE_DEPARTMENTLEADER) { }
protected override void DoHandleVacationRequest(VacationRequest request)
{
Console.WriteLine("Now department leader handle this request");
Console.WriteLine("Department leader accept this request");
}
}
class Boss : VacationApprover
{
private const int DAY_CAN_HANDLE_BOSS = 10;
public Boss() : base(DAY_CAN_HANDLE_BOSS) { }
protected override void DoHandleVacationRequest(VacationRequest request)
{
Console.WriteLine("Now boss handle this request");
Console.WriteLine("Boss accept this request");
}
}
```
###### 請假審批系統
請假審批系統提供統一請假申請介面,內部通過請假天數決定哪個審批者參與審批
```csharp
class VacationApproveSystem
{
private VacationApprover teamLeader = new TeamLeader();
private VacationApprover departmentLeader = new DepartmentLeader();
private VacationApprover boss = new Boss();
public void HandleVacationRequest(VacationRequest request)
{
Console.WriteLine("Now handle {0}'s {1} days' vacation request", request.RequesterName, request.DayNum);
if (request.DayNum <= teamLeader.DayCanHandle)
{
teamLeader.HandleVacationRequest(request);
}
else if (request.DayNum <= departmentLeader.DayCanHandle)
{
departmentLeader.HandleVacationRequest(request);
}
else if (request.DayNum <= boss.DayCanHandle)
{
boss.HandleVacationRequest(request);
}
else
{
Console.WriteLine("Cannot handle this request after all");
}
}
}
```
###### 測試程式碼
```csharp
class Program
{
static void Main(string[] args)
{
VacationApproveSystem system = new VacationApproveSystem();
system.HandleVacationRequest(new VacationRequest() { DayNum = 5, RequesterName = "laohu" });
system.HandleVacationRequest(new VacationRequest() { DayNum = 10, RequesterName = "laohu" });
system.HandleVacationRequest(new VacationRequest() { DayNum = 12, RequesterName = "laohu" });
}
}
```
結果顯示
![](https://img2020.cnblogs.com/blog/699616/202007/699616-20200708230407405-1842054004.png)
一切都是正常的,當5天時,部門經理審批,10天時,老闆審批,大於10天無人能批。 Good job。
#### 回頭看看
實現了第一版程式碼之後,我們再回過頭看看,雖然程式碼功能無誤,但是**VacationApproveSystem**似乎承擔了過多的職責,它不但需要提供統一的請假審批介面給終端使用者,它同時還需要知道每個請假審批者能審批的請假天數並在內部實現請假請求轉發給不同審批者的邏輯。這樣既違反了迪米特法則——它知道的太多了,也違反了開閉原則——如果任何一個審批者修改了自身能審批的請假天數,這個類都會被波及,最後,它還違反了單一職責——一個類只能有一個引起變化的原因。
有鑑於此,我們這版程式碼只能算湊合用,但遠遠談不上結構良好,老老實實地重構程式碼吧,下面請出我們今天的主角。
#### 職責鏈模式
![](https://img2020.cnblogs.com/blog/699616/202007/699616-20200708230443830-1289242990.png)
>解耦具體物件和請求,使得多個物件都有機會處理請求。將物件連成一條鏈,沿著鏈傳遞請求直到有物件處理它
乍一聽有點生澀,翻譯一下就是
- 解耦具體物件和請求——不要預先指定哪個物件來處理此請求(因為很多時候並不知道)
- 使多個物件都有機會——有一眾候選物件,具體使用哪個物件是在執行時決定的
- 連成鏈傳遞請求——像連結串列一樣,要在物件中體現出物件之間的鏈關係,而不要通過其他類以if..else的方式實現
所以,這麼看來這個模式和我們的例子簡直是絕配,我們已經做了大部分的工作了,現在剩下的就只是修改審批者,讓審批者能**鏈**起來
#### 程式碼重構
##### 修改請假審批基類
最重要的改動,就是修改基類,讓物件能**鏈**起來,在**VacationApprover**中新增一個後繼節點和一個設定後繼節點的方法。同時在基類的審批方法中,完成請求傳遞,即,如果請假申請超過了當前審批人的能力範圍,則轉發至後繼節點。修改後的類如下
```csharp
abstract class VacationApprover
{
private VacationApprover nextVacationApprover = null;
public void SetNextVacationApprover(VacationApprover approver)
{
nextVacationApprover = approver;
}
protected VacationApprover(int dayCanHandle)
{
DayCanHandle = dayCanHandle;
}
public int DayCanHandle { get; protected set; }
public void HandleVacationRequest(VacationRequest request)
{
if (request.DayNum <= DayCanHandle)
{
DoHandleVacationRequest(request);
}
else
{
if(nextVacationApprover != null)
{
nextVacationApprover.HandleVacationRequest(request);
}
else
{
Console.WriteLine("Cannot handle this request after all");
}
}
}
protected abstract void DoHandleVacationRequest(VacationRequest request);
}
```
##### 修改請假審批系統
基類重構結束之後,請假審批系統就可以瘦身了,刪除了所有判斷邏輯,僅僅在建構函式裡面完成**鏈**組建的工作,接著一鍵呼叫,齊活。
```csharp
class VacationApproveSystem
{
private VacationApprover teamLeader = new TeamLeader();
private VacationApprover departmentLeader = new DepartmentLeader();
private VacationApprover boss = new Boss();
public VacationApproveSystem()
{
teamLeader.SetNextVacationApprover(departmentLeader);
departmentLeader.SetNextVacationApprover(boss);
}
public void HandleVacationRequest(VacationRequest request)
{
Console.WriteLine("Now handle {0}'s {1} days' vacation request", request.RequesterName, request.DayNum);
teamLeader.HandleVacationRequest(request);
}
}
```
##### 測試
其他請假審批子類和測試客戶端都不需要改動,這次重構工作量非常小,執行程式碼,一切正常,重構成功。
#### 總結
這就是職責鏈模式的使用。和[狀態模式](https://www.cnblogs.com/deatharthas/p/13221496.html)有點像,解決了以下問題:
- 通過新增子類把一些邏輯判斷從呼叫類(VaccationApproveSystem)移到子類的方式,使得呼叫類滿足迪米特法則
- 想在職責鏈上面新增更多節點的時候,只需要新增新類和修改鏈組裝部分的程式碼,基本滿足開閉原則(這裡幾乎不可能完全滿足開閉原則,畢竟有修改就意味著我們肯定會改動VaccationApproveSystem類,只是我們應該儘量的讓程式碼改動量少,以提高控制程式碼變動的能力)
和狀態模式一樣,它也有子類爆炸的風險。
可能有朋友會感到疑惑,既然職責鏈模式和狀態模式看起來那麼像,那它們有什麼區別呢?它們的區別在於:
- 狀態模式中的物件是有狀態的,可以隨時通過介面查詢物件的當前狀態,物件正是因為有了不同的狀態,才會表現出不同行為。而職責鏈模式中的物件沒有狀態,物件和鏈的關係更像請求和處理管線的關係,沒有介面能告訴我們當前在處理管線的哪個節點,也沒有意義這麼做,我們只關心請求是否被處理了
- 狀態模式中的狀態切換可以是無序的,比如,一個遊戲角色,當他的狀態是虛弱的時候,可以通過治療,轉換成健康,也可以通過受傷轉換成瀕死。而職責鏈中的請求轉發就只有向前一條路,從小組長到部門經理,從部門經理到老闆
**根據不同的情景,選擇合適的模式,才是正確的使用之道**。以上就是今天的內容,希望大家喜歡,我們下