
相信大家都玩過類似于“鬥地主”的紙牌遊戲,某人出牌給他的下家,下家看看手中的牌,如果要不起,則将出牌請求轉發給他的下家,其下家再進行判斷。一個循環下來,如果其他人都要不起該牌,則最初的出牌者可以打出新牌。在這個過程中,紙牌作為一個請求沿着一條鍊在傳遞,每一位紙牌的玩家都可以處理該請求。在設計模式中,也有一種專門用于處理這種請求鍊式的模式,它就是職責鍊模式。
職責鍊模式(Chain of Responsibility) | 學習難度:★★★☆☆ | 使用頻率:★★☆☆☆ |
一、采購單的分級審批子產品設計
需求背景:M公司承接了某企業SCM(Supply Chain Management,供應鍊管理)系統的開發任務,其中包含一個采購審批子系統。該企業的采購審批是分級進行的,即根據采購金額的不同由不同層次的主管人員來審批:主任可以審批5萬元以下(不包括5萬)的采購單,副董事長可以審批5萬~10萬(不包括10萬)的采購單,50萬元以及以上的采購單就需要開董事會讨論決定,如下圖所示:![]()
設計模式的征途—14.職責鍊(Chain of Responsibility)模式
M公司開發人員提出了一個初始解決方案,提供了一個采購單處理類PurchaseRequestHandler用于統一處理采購單,其架構代碼如下:
/// <summary>
/// 采購單處理類
/// </summary>
public class PurchaseRequestHandler
{
// 遞交采購單給審批者
public void SendRequestToApprover(PurchaseRequest request)
{
if (request.Amount < 5000) // 主任可審批該采購單
{
HandleByDirector(request);
}
else if(request.Amount < 100000) // 副董事長可審批該采購單
{
HandleByVicePresident(request);
}
else if (request.Amount < 500000) // 董事長可審批該采購單
{
HandleByPresident(request);
}
else
{
HandleByCongress(request); // 董事會可審批該采購單
}
}
// 主管審批采購單
private void HandleByDirector(PurchaseRequest request)
{
// 代碼省略
}
// 副董事長審批采購單
private void HandleByVicePresident(PurchaseRequest request)
{
// 代碼省略
}
// 董事長審批采購單
private void HandleByPresident(PurchaseRequest request)
{
// 代碼省略
}
// 董事會審批采購單
private void HandleByCongress(PurchaseRequest request)
{
// 代碼省略
}
}
不過仔細分析後發現,上述方案存在以下3個問題:
(1)PurchaseRequestHandler類較為龐大,各個級别的審批方法都集中在一個類中,違反了單一職責原則,測試和維護難度較大。
(2)如果需要新增一個新的審批級别或調整任何一級的審批金額和審批細節時都必須修改源代碼并進行嚴格測試。此外,如果需要移除某一級别時也需要對源代碼進行修改,違反了開閉原則。
(3)審批流程的設定缺乏靈活性,現在的審批流程是“主任->副董事長->董事長->董事會”,如果需要改為“主任->董事長->董事會”,在此方案中隻能通過修改源代碼來實作,用戶端無法定制審批流程。
那麼如何破呢?别急,來看看職責鍊模式。
二、職責鍊模式概述
2.1 職責鍊模式簡介
職責鍊(Chain of Responsibility)模式:避免将請求發送者與接受者耦合在一起,讓多個對象都有機會接受請求,将這些對象連成一條鍊,并且沿着這條鍊傳遞請求,直到有對象處理它為止。職責鍊模式是一種對象行為型模式。
2.2 職責鍊模式結構
職責鍊模式結構的核心就在于引入了一個抽象處理者,其結構如下圖所示:
在職責鍊模式結構圖中包含以下兩個角色:
(1)Handler(抽象處理者):定義了一個處理請求的接口,一般設計為抽象類,由于不同的具體處理者處理請求的方式不同,是以在其中定義了抽象請求處理方法。
(2)ConcreteHandler(具體處理者):它是抽象處理者的子類,可以處理使用者請求,它實作了在抽象處理者中定義的抽象請求處理方法。在處理請求之前需要判斷是否有相應的處理權限,如果可以則處理,否則則将請求轉發給後繼者。
三、重構采購單分級審批子產品
3.1 重構後的設計
其中,抽象類Approver充當抽象處理類,Director, VicePresident, President以及Congress 充當具體處理者,PurchaseRequest充當請求類。
3.2 具體代碼實作
(1)請求類:PurchaseRequest
/// <summary>
/// 采購單:請求類
/// </summary>
public class PurchaseRequest
{
// 采購金額
public double Amount { get; set; }
// 采購單編号
public string Number { get; set; }
// 采購目的
public string Purpose { get; set; }
public PurchaseRequest(double amount, string number, string purpose)
{
Amount = amount;
Number = number;
Purpose = purpose;
}
}
(2)抽象處理者:Approver
/// <summary>
/// 審批者類:抽象處理者
/// </summary>
public abstract class Approver
{
protected Approver successor; // 定義後繼對象
protected string name; // 審批者姓名
public Approver(string name)
{
this.name = name;
}
// 設定後繼者
public void SetSuccessor(Approver successor)
{
this.successor = successor;
}
// 抽象請求處理方法
public abstract void ProcessRequest(PurchaseRequest request);
}
(3)具體處理者:Director, VicePresident, President以及Congress
/// <summary>
/// 總監:具體處理類
/// </summary>
public class Director : Approver
{
public Director(string name) : base(name)
{
}
// 具體請求處理方法
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < 50000)
{
// 處理請求
Console.WriteLine("主管 {0} 審批采購單:{1},金額:{2} 元,采購目的:{3}。",
this.name, request.Number, request.Amount, request.Purpose);
}
else
{
// 如果處理不了,轉發請求給更高層上司
this.successor.ProcessRequest(request);
}
}
}
/// <summary>
/// 副總裁:具體處理類
/// </summary>
public class VicePresident : Approver
{
public VicePresident(string name) : base(name)
{
}
// 具體請求處理方法
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < 100000)
{
// 處理請求
Console.WriteLine("副總裁 {0} 審批采購單:{1},金額:{2} 元,采購目的:{3}。",
this.name, request.Number, request.Amount, request.Purpose);
}
else
{
// 如果處理不了,轉發請求給更高層上司
this.successor.ProcessRequest(request);
}
}
}
/// <summary>
/// 總裁:具體處理者
/// </summary>
public class President : Approver
{
public President(string name) : base(name)
{
}
// 具體請求處理方法
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < 500000)
{
// 處理請求
Console.WriteLine("總裁 {0} 審批采購單:{1},金額:{2} 元,采購目的:{3}。",
this.name, request.Number, request.Amount, request.Purpose);
}
else
{
// 如果處理不了,轉發請求給更高層上司
this.successor.ProcessRequest(request);
}
}
}
/// <summary>
/// 董事會:具體處理者
/// </summary>
public class Congress : Approver
{
public Congress(string name) : base(name)
{
}
// 具體請求處理方法
public override void ProcessRequest(PurchaseRequest request)
{
// 處理請求
Console.WriteLine("董事會 {0} 審批采購單:{1},金額:{2} 元,采購目的:{3}。",
this.name, request.Number, request.Amount, request.Purpose);
}
}
(4)用戶端測試:
public class Program
{
public static void Main(string[] args)
{
// 建立職責鍊
Approver andy = new Director("Andy");
Approver jacky = new VicePresident("Jacky");
Approver ashin = new President("Ashin");
Approver meeting = new Congress("Congress");
andy.SetSuccessor(jacky);
jacky.SetSuccessor(ashin);
ashin.SetSuccessor(meeting);
// 構造采購請求單并發送審批請求
PurchaseRequest request1 = new PurchaseRequest(45000.00,
"MANULIFE201706001",
"購買PC和顯示器");
andy.ProcessRequest(request1);
PurchaseRequest request2 = new PurchaseRequest(60000.00,
"MANULIFE201706002",
"2017開發團隊活動");
andy.ProcessRequest(request2);
PurchaseRequest request3 = new PurchaseRequest(160000.00,
"MANULIFE201706003",
"2017公司年度旅遊");
andy.ProcessRequest(request3);
PurchaseRequest request4 = new PurchaseRequest(800000.00,
"MANULIFE201706004",
"租用新臨時辦公樓");
andy.ProcessRequest(request4);
Console.ReadKey();
}
}
編譯運作後的結果如下圖所示:
3.3 需求擴充實作
這時,假設需要在系統中新增一個新的具體處理者,例如增加一個經理(Manager)角色可以審批5萬~8萬(不包括8萬)的采購單。是以,我們可以新增一個具體處理者:Manager
/// <summary>
/// 經理:具體處理者
/// </summary>
public class Manager : Approver
{
public Manager(string name) : base(name)
{
}
// 具體請求處理方法
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < 80000)
{
// 處理請求
Console.WriteLine("經理 {0} 審批采購單:{1},金額:{2} 元,采購目的:{3}。",
this.name, request.Number, request.Amount, request.Purpose);
}
else
{
this.successor.ProcessRequest(request);
}
}
}
由于鍊的建立過程由用戶端負責,是以此擴充對原有類庫無任何影響,符合開閉原則。而我們需要做的,僅僅是在用戶端代碼中新增職責鍊關系的建立即可。
public class Program
{
public static void Main(string[] args)
{
// 建立職責鍊
Approver andy = new Director("Andy");
Approver jacky = new Manager("Jacky");
Approver ashin = new VicePresident("Ashin");
Approver anya = new President("Anya");
Approver meeting = new Congress("Congress");
andy.SetSuccessor(jacky);
jacky.SetSuccessor(ashin);
ashin.SetSuccessor(anya);
anya.SetSuccessor(meeting);
// 構造采購請求單并發送審批請求
PurchaseRequest request1 = new PurchaseRequest(45000.00,
"MANULIFE201706001",
"購買PC和顯示器");
andy.ProcessRequest(request1);
PurchaseRequest request2 = new PurchaseRequest(60000.00,
"MANULIFE201706002",
"2017開發團隊活動");
andy.ProcessRequest(request2);
PurchaseRequest request3 = new PurchaseRequest(160000.00,
"MANULIFE201706003",
"2017公司年度旅遊");
andy.ProcessRequest(request3);
PurchaseRequest request4 = new PurchaseRequest(800000.00,
"MANULIFE201706004",
"租用新臨時辦公樓");
andy.ProcessRequest(request4);
Console.ReadKey();
}
}
重新編譯運作後的結果如下圖所示:
四、職責鍊模式總結
4.1 主要優點
(1)使得一個對象無需知道是其他哪一個對象處理其請求,對象僅需知道該請求會被處理即可,且鍊式結構由用戶端建立 => 降低了系統的耦合度
(2)在系統中增加一個新的具體處理者無須修改原有系統源代碼,隻需要在用戶端重建立立鍊式結構即可 => 符合開閉原則
4.2 主要缺點
(1)由于一個請求沒有一個明确地接受者 => 無法保證它一定會被處理
(2)對于較長的職責鍊 => 系統性能有一定影響且不利于調試
(3)如果建立鍊不當,可能會造成循環調用 => 導緻系統進入死循環
4.3 應用場景
(1)有多個對象處理同一個請求且無需關心請求的處理對象時誰以及它是如何處理的 => 比如各種審批流程
(2)可以動态地指定一組對象處理請求,用戶端可以動态建立職責鍊來處理請求,還可以改變鍊中處理者之間的先後次序 => 比如各種流程定制
參考資料
劉偉,《設計模式的藝術—軟體開發人員内功修煉之道》
作者:周旭龍
出處:http://edisonchou.cnblogs.com
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連結。