定義:(Chain of Responsibility)
使多個對象都有機會處理請求,從而避免了請求的發送者和接受者之間的耦合關系。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有對象處理它為止。
類圖:
啟示:
用過ERP軟件的都知道,從生產制造到采購銷售,庫存管理到財務管理,各個模塊下各個環節基本上都是按照既定業務流程進行規范作業的。各個環節都有對應的人員負責,承擔相應職責。
拿采購環節舉例:
開發經理向公司反饋需要要給程序猿升級設備以提高工作效率,申請采購一批電腦設備。公司采購部門有采購員小責做了一張采購申請單,保存后,需要直接上級采購經理任經理確認提交審核,若總金額超過經理審核最大額度,需繼續提交到公司老總審批,只有CEO鏈總批準后,這張采購申請單才能生效。
這個環節中共包含三個主要操作,保存、提交、審核,且分別對應三個角色進行處理。
在這個例子中,采購員負責做單;采購經理負責檢查確認;CEO負責審核。
采購員具有權限:保存
采購經理具有權限:保存、提交、審核(只可審核總金額低于5萬的訂單)
CEO具有權限:保存、提交、審核
這不就是一條業務被不同職責分割成三個操作嗎?
來,咱們用『責任鏈模式』一探究竟。
代碼:
先來定義單據,單據主要包括單據名稱、單據編號、物料、數量、價格、總金額,還有制單員
/// <summary>
/// 單據狀態枚舉
/// </summary>
public enum BillStatus
{
Open,
Saved,
Submitted,
Audited
}
public abstract class Bill
{
public string BillName { get; set; }
public string BilNo { get; set; }
public BillStatus Status { get; set; }
public string MaterialName { get; set; }
public int Qty { get; set; }
public decimal Price { get; set; }
public decimal Amount
{
get
{
return Qty * Price;
}
}
/// <summary>
/// 做單員
/// </summary>
public BillHandler BillMaker { get; set; }
}
public class PurchaseBill : Bill
{
public PurchaseBill()
{
base.Status = BillStatus.Open;
base.BillName = "采購申請單";
}
}
細心的同學注意到我們單據中定義的制單員BillMaker
引入了BillHandler
,這個就是我們『責任鏈模式』的核心所在,Handler角色。主要定義并實現了一個HandleBill方法(模板方法模式)供子類共用;同時定義了一個抽象方法DoBillOperation讓子類實現自己獨立的業務邏輯;還有一個重要的屬性Successor
,即下一處理者,是流程流轉的必要屬性啊,切記切記。
另外:單據的審批流是基于角色、權限的,所以我在BillHandler類定義了Permissions權限集合,和公共的權限檢查方法。
上代碼:
/// <summary>
/// 單據處理角色
/// </summary>
public abstract class BillHandler
{
/// <summary>
/// 單據處理者姓名
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 單據處理者具有的權限
/// </summary>
public List<string> Permissions { get; set; }
public bool CheckPermission(string permission)
{
return Permissions.Contains(permission);
}
//聲明下一個處理者
protected BillHandler Successor { get; set; }
/// <summary>
/// 處理單據
/// </summary>
/// <param name="bill"></param>
public void HandleBill(Bill bill)
{
//單據處理從保存開始
if (CheckPermission("SAVE") && bill.Status == BillStatus.Open)
{
this.DoBillOperation(bill);
}
else
{
this.Successor.DoBillOperation(bill);
}
}
public abstract void DoBillOperation(Bill bill);
}
先來看看采購員的實現:權限是動態的,為了演示方便,我們在代碼中寫死權限。雖然采購員只有保存權限,但是我們還是給他實現了提交、審核的邏輯。因為權限是動態的嘛。(代碼中限定只可審核金額低于5000元的單);且在默認構造函數指定下一流程處理者(經理)
public class Purchaser : BillHandler
{
private List<string> permissions = new List<string>() { "SAVE" };
public Purchaser(string username)
{
base.UserName = username;
base.Permissions = permissions;
base.Successor = new Manager("經理--任經理");//在構造函數中指定下一個處理者
}
public override void DoBillOperation(Bill bill)
{
if (CheckPermission("SAVE") && bill.Status == BillStatus.Open)
{
bill.Status = BillStatus.Saved;
Console.WriteLine(string.Format("{0}:{1}已經保存!", this.UserName, bill.BilNo));
}
if (CheckPermission("SUBMIT") && bill.Status == BillStatus.Saved)
{
bill.Status = BillStatus.Submitted;
Console.WriteLine(string.Format("{0}:{1}已經提交!", this.UserName, bill.BilNo));
}
if (CheckPermission("AUDIT") && bill.Status == BillStatus.Submitted)
{
if (bill.Amount <= 5000)
{
bill.Status = BillStatus.Submitted;
Console.WriteLine(string.Format("{0}:{1}已經審核!", this.UserName, bill.BilNo));
}
else
{
this.Successor.DoBillOperation(bill);
}
}
else
{
this.Successor.DoBillOperation(bill);
}
}
}
下面來看看經理,在默認構造函數指定下一流程處理者(CEO),同時在業務邏輯中限定只可審核金額低于50000元的單。
public class Manager : BillHandler
{
private List<string> permissions = new List<string>() { "SAVE", "SUBMIT", "AUDIT" };
public Manager(string userName)
{
base.UserName = userName;
base.Permissions = permissions;
base.Successor = new CEO("CEO--鏈總");
}
public override void DoBillOperation(Bill bill)
{
if (CheckPermission("SAVE") && bill.Status == BillStatus.Open)
{
bill.Status = BillStatus.Saved;
Console.WriteLine(string.Format("{0}:{1}已經保存!", this.UserName, bill.BilNo));
}
if (CheckPermission("SUBMIT") && bill.Status == BillStatus.Saved)
{
bill.Status = BillStatus.Submitted;
Console.WriteLine(string.Format("{0}:{1}已經提交!", this.UserName, bill.BilNo));
}
if (CheckPermission("AUDIT") && bill.Status == BillStatus.Submitted)
{
if (bill.Amount <= 20000)
{
bill.Status = BillStatus.Submitted;
Console.WriteLine(string.Format("{0}:{1}已經審核!", this.UserName, bill.BilNo));
}
else
{
this.Successor.DoBillOperation(bill);
}
}
else
{
this.Successor.DoBillOperation(bill);
}
}
}
最后上公司CEO,,注意流程結束了,就不需要指定下一流程處理者了:
public class CEO : BillHandler
{
private List<string> permissions = new List<string>() { "SAVE", "SUBMIT", "AUDIT" };
public CEO(string userName)
{
base.UserName = userName;
base.Permissions = permissions;
}
public override void DoBillOperation(Bill bill)
{
if (CheckPermission("SAVE") && bill.Status == BillStatus.Open)
{
bill.Status = BillStatus.Saved;
Console.WriteLine(string.Format("{0}:{1}已經保存!", this.UserName, bill.BilNo));
}
if (CheckPermission("SUBMIT") && bill.Status == BillStatus.Saved)
{
bill.Status = BillStatus.Submitted;
Console.WriteLine(string.Format("{0}:{1}已經提交!", this.UserName, bill.BilNo));
}
if (CheckPermission("AUDIT") && bill.Status == BillStatus.Submitted)
{
bill.Status = BillStatus.Submitted;
Console.WriteLine(string.Format("{0}:{1}已經審核!", this.UserName, bill.BilNo));
}
}
}
看看測試代碼及結果
static void Main(string[] args)
{
PurchaseBill bill = new PurchaseBill()
{
BilNo = "CGDD001",
MaterialName = "惠普電腦",
Qty = 50,
Price = 5000,
BillMaker = new Purchaser("采購員--小責")
//BillMaker = new Manager("經理--任經理")
//BillMaker = new CEO("CEO--鏈總")
};
Console.WriteLine(string.Format("創建采購申請單:{0};申請購買{1}臺{2}", bill.BilNo, bill.Qty, bill.MaterialName));
bill.BillMaker.HandleBill(bill);
Console.ReadLine();
}
從中我們可以看到,我們是通過制單員去調用單據處理方法,同時我們可以靈活替換掉制單員,因為實際業務場景也不是限定只有采購員才可以做單的嘛。感興趣的可以更改數量,看看不同的金額下,最終走了哪些流程。
優缺點:
優點:
責任鏈模式非常顯著的優點是將請求和處理分開。請求者可以不用知道是誰處理的,處理者可以不用知道請求的全貌,兩者解耦,提高系統的靈活性。
缺點:
責任鏈有兩個非常顯著的缺點:一是性能問題,每個請求都是從鏈頭遍歷到鏈尾,特別是在鏈比較長的時候,性能是一個非常大的問題。二是調試不很方便,特別是鏈條比較長,環節比較多的時候,由于采用了類似遞歸的方式,調試的時候邏輯可能比較復雜。
應用場景:
流程業務,各司其職,就可以考慮『責任鏈模式』
總結:
『責任鏈模式』其實是『模板方法模式』的拓展,責任鏈模式的重點是在“鏈”上,由一條鏈去處理相似的請求在鏈中決定誰來處理這個請求,并返回相應的結果。