引子:加薪非要老總批?#
小菜試用三個月即將轉正,向經理提加薪。流程是:
- 經理:「我做不了主,幫你向上提一提」 → 找人力資源總監
- 總監:「沒有先例,這事要等總經理來了再提」 → 找總經理
- 總經理:「畢業生這麼多,三個月就想加薪不合適」
加薪、請假等請求的處理:不同層級有不同的權限,請求逐級上交,直到有人能處理。
這正是職責鏈模式的典型場景。
第一版:一個 Manager 類負責所有層級#
class Request
{
public string RequestType { get; set; }
public string RequestContent { get; set; }
public int Number { get; set; }
}
class Manager
{
protected string name;
public Manager(string name) { this.name = name; }
public void GetResult(string managerLevel, Request request)
{
if (managerLevel == "經理") { /* 請假 ≤ 2 天才批准 */ }
else if (managerLevel == "總監") { /* 請假 ≤ 5 天才批准 */ }
else if (managerLevel == "總經理") { /* 加薪 ≤ 500 批准,> 500 再說吧 */ }
}
}問題:
- 方法過長、多條分支判斷——典型壞味道
- 新增管理者類別(部門經理、副總……)要修改這個類,違反開放-封閉原則
- 一個類承擔太多職責——違反單一職責原則
職責鏈模式#
職責鏈模式(Chain of Responsibility):使多個物件都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它為止。[DP]
關鍵思想:發出請求的客戶端並不知道哪個物件最終會處理這個請求——系統可以動態地重組責任。
結構#
- Handler(抽象處理者):定義一個處理請求的介面,並設定後繼者
- ConcreteHandler(具體處理者):處理它所負責的請求;可訪問後繼者,能處理就處理,否則轉發給後繼者
classDiagram
class Client
class Handler {
<<abstract>>
#Handler successor
+SetSuccessor(Handler)
+HandleRequest(int)*
}
class ConcreteHandler1
class ConcreteHandler2
class ConcreteHandler3
Client --> Handler
Handler <|-- ConcreteHandler1
Handler <|-- ConcreteHandler2
Handler <|-- ConcreteHandler3
Handler --> Handler : successorsequenceDiagram
participant 小菜
participant 經理
participant 總監
participant 總經理
小菜->>經理: 加薪 1000 元申請
經理->>總監: 我無權處理(轉發)
總監->>總經理: 我無權處理(轉發)
總經理-->>小菜: 加薪 > 500,再說吧abstract class Handler
{
protected Handler successor;
public void SetSuccessor(Handler successor) => this.successor = successor;
public abstract void HandleRequest(int request);
}
class ConcreteHandler1 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 0 && request < 10)
Console.WriteLine($"{GetType().Name} 處理請求 {request}");
else if (successor != null)
successor.HandleRequest(request);
}
}客戶端:
Handler h1 = new ConcreteHandler1();
Handler h2 = new ConcreteHandler2();
Handler h3 = new ConcreteHandler3();
h1.SetSuccessor(h2);
h2.SetSuccessor(h3);
foreach (int req in new[] { 2, 5, 14, 22, 18 })
h1.HandleRequest(req);重構加薪範例#
把原來的單一 Manager 類拆成抽象類 + 三個具體類:
abstract class Manager
{
protected string name;
protected Manager superior;
public Manager(string name) { this.name = name; }
public void SetSuperior(Manager superior) => this.superior = superior;
public abstract void RequestApplications(Request request);
}
class CommonManager : Manager // 經理
{
public CommonManager(string name) : base(name) { }
public override void RequestApplications(Request request)
{
if (request.RequestType == "請假" && request.Number <= 2)
Console.WriteLine($"{name}: {request.RequestContent} 數量 {request.Number} 被批准");
else if (superior != null)
superior.RequestApplications(request);
}
}
class Majordomo : Manager // 總監
{
public Majordomo(string name) : base(name) { }
public override void RequestApplications(Request request)
{
if (request.RequestType == "請假" && request.Number <= 5)
Console.WriteLine($"{name}: {request.RequestContent} 數量 {request.Number} 被批准");
else if (superior != null)
superior.RequestApplications(request);
}
}
class GeneralManager : Manager // 總經理
{
public GeneralManager(string name) : base(name) { }
public override void RequestApplications(Request request)
{
if (request.RequestType == "請假")
Console.WriteLine($"{name}: {request.RequestContent} 數量 {request.Number} 被批准");
else if (request.RequestType == "加薪" && request.Number <= 500)
Console.WriteLine($"{name}: {request.RequestContent} 數量 {request.Number} 被批准");
else if (request.RequestType == "加薪" && request.Number > 500)
Console.WriteLine($"{name}: {request.RequestContent} 數量 {request.Number} 再說吧");
}
}客戶端:
CommonManager jinli = new CommonManager("金利");
Majordomo zongjian = new Majordomo("宗劍");
GeneralManager zhongjingli = new GeneralManager("鍾精勵");
jinli.SetSuperior(zongjian);
zongjian.SetSuperior(zhongjingli);
Request r = new Request { RequestType = "加薪", RequestContent = "小菜請求加薪", Number = 1000 };
jinli.RequestApplications(r); // 由「經理」發起,但實際處理者由鏈決定客戶端的所有申請都從「經理」發起,實際處理由具體類別決定,客戶端無需關心。
職責鏈的好處#
- 接收者和發送者都沒有對方的明確資訊
- 鏈中的物件自己也不知道鏈的結構
- 物件僅需保持一個指向後繼者的引用,大大降低耦合度[DP]
- 在客戶端定義鏈的結構,可隨時增加或修改處理請求的結構——增強指派職責的靈活性[DP]
注意事項#
一個請求極有可能到了鏈的末端都得不到處理,或因配置不正確而得不到處理——必須事先考慮全面。
就像現實中郵寄一封信因地址不對最終無法送達一樣。
兩個關鍵點:
- 事先給每個具體處理者設置上級(後繼者)
- 每個處理者要正確判斷:自己處理還是「推卸責任」轉給後繼者
結局#
故事的結局:經理跳過總監直接找總經理談,最終為小菜爭取到加薪——
經理改變了職責鏈的結構(跳過總監),這也是職責鏈靈活性的體現。