詳解如何在C#/.NET Core中使用責(zé)任鏈模式
最近我有一個(gè)朋友在研究經(jīng)典的“Gang Of Four”設(shè)計(jì)模式。他經(jīng)常來(lái)詢(xún)問(wèn)我在實(shí)際業(yè)務(wù)應(yīng)用中使用了哪些設(shè)計(jì)模式。單例模式、工廠(chǎng)模式、中介者模式 - 都是我之前使用過(guò),甚至寫(xiě)過(guò)相關(guān)文章的模式。但是有一種模式是我還沒(méi)有寫(xiě)過(guò)文章,即責(zé)任鏈模式。
什么是責(zé)任鏈?#
責(zé)任鏈模式(之前我經(jīng)常稱(chēng)之為命令鏈模式)是一種允許以使用分層方式”處理“對(duì)象的模式。在維基百科中的經(jīng)典定義是
在面向?qū)ο笤O(shè)計(jì)中,責(zé)任鏈模式是一種由命令對(duì)象源及其一系列處理對(duì)象組成的設(shè)計(jì)模式。每個(gè)處理對(duì)象包含了它可以處理的命令對(duì)象的邏輯,其余的將傳遞給鏈中的下一個(gè)處理對(duì)象。當(dāng)然,這里還存在一種將新的處理對(duì)象追加到鏈尾的機(jī)制。因此責(zé)任鏈?zhǔn)荌f..else if.. else if...else...endif的面向?qū)ο蟀姹?。其?yōu)點(diǎn)是可以在運(yùn)行時(shí)動(dòng)態(tài)重新排列或配置條件操作塊。
也許你會(huì)覺(jué)著上面的概念描述過(guò)于抽象,不容易理解,那么下面讓我們來(lái)看一個(gè)真實(shí)生活中的例子。
這里假設(shè)我們擁有一家銀行,銀行里面有3個(gè)級(jí)別的員工,分別是“柜員”、“主管”、“銀行經(jīng)理”。如果有人來(lái)取款,“柜員”只允許10,000美元以下的取款操作。如果金額超過(guò)10,000美元,那么它的請(qǐng)求將傳遞給“主管”?!爸鞴堋笨梢蕴幚聿怀^(guò)100,000美元的請(qǐng)求,但前提是該賬戶(hù)在必須有身份證ID。如果沒(méi)有身份證ID,則當(dāng)前請(qǐng)求必須被拒絕。如果取款金額超過(guò)100,000美元,則當(dāng)前請(qǐng)求可以轉(zhuǎn)交給“銀行經(jīng)理”,“銀行經(jīng)理”可以批準(zhǔn)任何取款金額,因?yàn)槿绻腥巳〕^(guò)100,000美元的金額,他們就是VIP, 我們不在乎VIP的身份證ID和其他規(guī)定。
這就是我們前面討論的分層“鏈”,每個(gè)人都嘗試處理當(dāng)前請(qǐng)求,如果沒(méi)有滿(mǎn)足要求,就傳遞給下一個(gè)。如果我們將這種場(chǎng)景轉(zhuǎn)換成代碼,就是我們所說(shuō)的責(zé)任鏈模式。但是在這之前,讓我們先來(lái)看一個(gè)糟糕的實(shí)現(xiàn)方法。
一個(gè)糟糕的實(shí)現(xiàn)方式#
下面我們先使用If/Else塊來(lái)解決當(dāng)前問(wèn)題。
class BankAccount
{
bool idOnRecord { get; set; }
void WithdrawMoney(decimal amount)
{
// 柜員處理
if(amount < 10000)
{
Console.WriteLine("柜員提取的金額");
}
// 主管處理
else if (amount < 100000)
{
if(!idOnRecord)
{
throw new Exception("客戶(hù)沒(méi)有身份證ID");
}
Console.WriteLine("主管提取的金額");
}
else
{
Console.WriteLine("銀行經(jīng)理提取的金額");
}
}
}
以上這種實(shí)現(xiàn)方式有幾個(gè)問(wèn)題:
- 添加一種新的員工級(jí)別會(huì)相當(dāng)困難,因?yàn)镮F/Else代碼塊看起來(lái)太亂了
- “主管”檢查身份證ID的邏輯在某種程度上很難進(jìn)行單元測(cè)試,因?yàn)樗仨毷紫韧ㄟ^(guò)其他的檢查
- 雖然現(xiàn)在我們只定義了提款金額的邏輯,但是如果在將來(lái)我們想要添加其他檢查(例如:VIP客戶(hù)始終由主管來(lái)處理), 這種邏輯將很難管理,并且很容易失控。
使用責(zé)任鏈模式編碼#
下面讓我們重寫(xiě)一些這部分代碼。與之前不同,這里我們創(chuàng)建一些“員工”對(duì)象,里面封裝了他們的處理邏輯。這里最重要的是,我們需要給每個(gè)員工對(duì)象指定一個(gè)直屬上級(jí),以便當(dāng)他們處理不了當(dāng)前請(qǐng)求的時(shí)候,可以將請(qǐng)求傳遞給直屬上級(jí)。
interface IBankEmployee
{
IBankEmployee LineManager { get; }
void HandleWithdrawRequest(BankAccount account, decimal amount);
}
class Teller : IBankEmployee
{
public IBankEmployee LineManager { get; set; }
public void HandleWithdrawRequest(BankAccount account, decimal amount)
{
if(amount > 10000)
{
LineManager.HandleWithdrawRequest(account, amount);
return;
}
Console.WriteLine("柜員提取的金額");
}
}
class Supervisor : IBankEmployee
{
public IBankEmployee LineManager { get; set; }
public void HandleWithdrawRequest(BankAccount account, decimal amount)
{
if (amount > 100000)
{
LineManager.HandleWithdrawRequest(account, amount);
return;
}
if(!account.idOnRecord)
{
throw new Exception("客戶(hù)沒(méi)有身份證ID");
}
Console.WriteLine("主管提取的金額");
}
}
class BankManager : IBankEmployee
{
public IBankEmployee LineManager { get; set; }
public void HandleWithdrawRequest(BankAccount account, decimal amount)
{
Console.WriteLine("銀行經(jīng)理提取的金額");
}
}
我們可以通過(guò)指定上級(jí)的方式創(chuàng)建出責(zé)任鏈。這看起來(lái)很像一個(gè)組織結(jié)構(gòu)圖。
var bankManager = new BankManager();
var bankSupervisor = new Supervisor { LineManager = bankManager };
var frontLineStaff = new Teller { LineManager = bankSupervisor };
這里我們可以創(chuàng)建一個(gè)BankAccount類(lèi),并將取款方法轉(zhuǎn)換為由前臺(tái)員工處理。
class BankAccount
{
public bool idOnRecord { get; set; }
public void WithdrawMoney(IBankEmployee frontLineStaff, decimal amount)
{
frontLineStaff.HandleWithdrawRequest(this, amount);
}
}
現(xiàn)在,當(dāng)我們進(jìn)行取款請(qǐng)求的時(shí)候,“柜員”總是第一個(gè)來(lái)處理,如果處理不了,它會(huì)自動(dòng)將請(qǐng)求發(fā)給直屬領(lǐng)導(dǎo)。這種模式的優(yōu)雅之處有以下幾點(diǎn):
- 鏈中的后續(xù)子項(xiàng)并不需要知道是哪個(gè)子項(xiàng)將命令傳遞給它的。就像這里,“主管”不需要知道是為什么下級(jí)“柜員”為什么會(huì)把請(qǐng)求傳遞給他
- "柜員"不需要知道整個(gè)鏈。他僅負(fù)責(zé)將請(qǐng)求傳遞給上級(jí)""主管"",期望請(qǐng)求能在上級(jí)“主管”那里被處理(當(dāng)前也許還需要進(jìn)一步的傳遞處理)即可
- 當(dāng)引入新員工類(lèi)型的時(shí)候,整個(gè)組織架構(gòu)圖很容易變更。例如, 我創(chuàng)建了一個(gè)新的“柜員經(jīng)理”角色,他能處理10,000-50,000美元之間的提款請(qǐng)求,“柜員經(jīng)理”的直屬上級(jí)是“主管”。這里我們并不需要對(duì)“主管”對(duì)象做任何的處理,只需要將“柜員”的直屬上級(jí)改為“柜員經(jīng)理”即可
- 當(dāng)編寫(xiě)單元測(cè)試的時(shí)候,我們可以一次只關(guān)注一個(gè)雇員角色了。例如,在測(cè)試“主管”邏輯的時(shí)候,我們就不需要測(cè)試“柜員”的邏輯了
擴(kuò)展我們的例子#
盡管我認(rèn)為以上的例子已經(jīng)能很好的說(shuō)明這種模式,但是通常你會(huì)發(fā)現(xiàn)有些人會(huì)使用一個(gè)方法叫做SetNext.一般來(lái)說(shuō),我覺(jué)著這在C#中是非常罕見(jiàn)的,因?yàn)镃#中我們可以使用屬性獲取器和設(shè)置器。使用SetVariableName方法通常都是C++時(shí)代的事情了,那時(shí)候這通常是封裝變量的首選方法。
但這里最重要的是,其他示例通常使用抽象類(lèi)來(lái)加強(qiáng)請(qǐng)求傳遞的方式。在前面代碼中有一個(gè)問(wèn)題是,將請(qǐng)求傳遞給下一個(gè)處理器的時(shí)候,編寫(xiě)了許多重復(fù)代碼。那么就讓我們來(lái)整理一下代碼。
這里我們要做的第一件事情就是創(chuàng)建一個(gè)抽象類(lèi),這個(gè)抽象類(lèi)使我們能夠通過(guò)標(biāo)準(zhǔn)化的方式處理提款請(qǐng)求。它應(yīng)該定義一個(gè)檢測(cè)條件,如果條件滿(mǎn)足,就執(zhí)行提款,反之,就將請(qǐng)求傳遞給直屬上級(jí)。經(jīng)過(guò)修改之后的代碼如下:
interface IBankEmployee
{
IBankEmployee LineManager { get; }
void HandleWithdrawRequest(BankAccount account, decimal amount);
}
abstract class BankEmployee : IBankEmployee
{
public IBankEmployee LineManager { get; private set; }
public void SetLineManager(IBankEmployee lineManager)
{
this.LineManager = lineManager;
}
public void HandleWithdrawRequest(BankAccount account, decimal amount)
{
if (CanHandleRequest(account, amount))
{
Withdraw(account, amount);
}
else
{
LineManager.HandleWithdrawRequest(account, amount);
}
}
abstract protected bool CanHandleRequest(BankAccount account, decimal amount);
abstract protected void Withdraw(BankAccount account, decimal amount);
}
下一步,我們需要修改所有的員工類(lèi),使其繼承自BankEmployee抽象類(lèi)
class Teller : BankEmployee, IBankEmployee
{
protected override bool CanHandleRequest(BankAccount account, decimal amount)
{
if (amount > 10000)
{
return false;
}
return true;
}
protected override void Withdraw(BankAccount account, decimal amount)
{
Console.WriteLine("柜員提取的金額");
}
}
class Supervisor : BankEmployee, IBankEmployee
{
protected override bool CanHandleRequest(BankAccount account, decimal amount)
{
if (amount > 100000)
{
return false;
}
return true;
}
protected override void Withdraw(BankAccount account, decimal amount)
{
if (!account.idOnRecord)
{
throw new Exception("客戶(hù)沒(méi)有身份證ID");
}
Console.WriteLine("主管提取的金額");
}
}
class BankManager : BankEmployee, IBankEmployee
{
protected override bool CanHandleRequest(BankAccount account, decimal amount)
{
return true;
}
protected override void Withdraw(BankAccount account, decimal amount)
{
Console.WriteLine("銀行經(jīng)理提取的金額");
}
}
這里請(qǐng)注意,在所有的場(chǎng)景中,都會(huì)調(diào)用抽象類(lèi)中的HandleWithdrawRequest公共方法。 該方法會(huì)調(diào)用子類(lèi)中定義的CanHandleRequest方法來(lái)檢測(cè)當(dāng)前角色是否滿(mǎn)足處理請(qǐng)求的條件,如果滿(mǎn)足,就調(diào)用子類(lèi)中的Withdraw方法處理請(qǐng)求,否則就會(huì)嘗試將請(qǐng)求傳遞給上級(jí)角色。
我們只需要像以下代碼這樣,更改創(chuàng)建員工鏈的方式即可:
var bankManager = new BankManager(); var bankSupervisor = new Supervisor(); bankSupervisor.SetLineManager(bankManager); var frontLineStaff = new Teller(); frontLineStaff.SetLineManager(bankSupervisor);
這里我需要再次重申,我并不喜歡使用SetXXX這種方法,但是許多例子中都喜歡這么使用,所以我就把它加了進(jìn)來(lái)。
在一些例子中,也會(huì)將判斷員工是否滿(mǎn)足處理請(qǐng)求的條件放在抽象類(lèi)中。我個(gè)人不喜歡這樣做,因?yàn)檫@意味著所有的處理程序不得不使用相似的邏輯。例如,目前所有的檢查都是基于提取金額的,但是如果我們想要實(shí)現(xiàn)一個(gè)特殊的處理程序,它的條件和VIP標(biāo)志有關(guān),那么我們將不得不又在抽象類(lèi)中重新使用IF/Else, 這又將我們帶回到了IF/Else地獄中。
什么時(shí)候應(yīng)該使用責(zé)任鏈模式?#
這種模式最佳的使用場(chǎng)景是,你的業(yè)務(wù)上有一個(gè)邏輯上的處理鏈,這個(gè)處理鏈每次必須按照順序運(yùn)行。這里請(qǐng)注意,鏈分叉是這種模式的一個(gè)變體, 但是很快處理起來(lái)就會(huì)非常復(fù)雜。因此,當(dāng)我對(duì)現(xiàn)實(shí)世界中“命令鏈”場(chǎng)景建模的時(shí)候,我通常會(huì)使用這種模式。這就是我以銀行為例的原因,因?yàn)樗褪乾F(xiàn)實(shí)世界中可以用代碼建模的“責(zé)任鏈”。
到此這篇關(guān)于詳解如何在C#/.NET Core中使用責(zé)任鏈模式的文章就介紹到這了,更多相關(guān)C#/.NET Core 責(zé)任鏈模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
原文:Chain Of Responsbility Pattern In C#/.NET Core
作者:Wade
譯者:Lamond Lu
出處:https://www.cnblogs.com/lwqlun/p/12846451.html
版權(quán):本文采用「署名 4.0 國(guó)際」知識(shí)共享許可協(xié)議進(jìn)行許可。
相關(guān)文章
C#結(jié)合JavaScript對(duì)Web控件進(jìn)行數(shù)據(jù)輸入驗(yàn)證的實(shí)現(xiàn)方法
在 Web 應(yīng)用的錄入界面,數(shù)據(jù)驗(yàn)證是一項(xiàng)重要的實(shí)現(xiàn)功能,數(shù)據(jù)驗(yàn)證是指確認(rèn) Web 控件輸入或選擇的數(shù)據(jù),本文我們將介紹如何通過(guò)C# 后端及JavaScript 前端對(duì) Web 控件進(jìn)行數(shù)據(jù)輸入有效性的驗(yàn)證,感興趣的朋友可以參考一下2024-05-05
如何使用Dapper處理多個(gè)結(jié)果集與多重映射實(shí)例教程
Dapper類(lèi)是一個(gè)開(kāi)源的數(shù)據(jù)庫(kù)操作類(lèi),下面這篇文章主要給大家介紹了關(guān)于如何使用Dapper處理多個(gè)結(jié)果集與多重映射的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-09-09
C#設(shè)置WinForm中DataGrid列的方法(列寬/列標(biāo)題等)
這篇文章主要介紹了C#設(shè)置WinForm中DataGrid列的方法,包括列寬、列標(biāo)題等部分,并分析了其中相關(guān)的操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07
基于Silverlight DataGrid中無(wú)代碼設(shè)置開(kāi)始與結(jié)束日期DatePicker的實(shí)現(xiàn)方法
本篇文章是對(duì)Silverlight DataGrid中無(wú)代碼設(shè)置開(kāi)始與結(jié)束日期DatePicker的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
sqlserver備份還原數(shù)據(jù)庫(kù)功能封裝分享
這篇文章主要介紹了sqlserver備份還原數(shù)據(jù)庫(kù)功能封裝示例,需要的朋友可以參考下2014-03-03
C#調(diào)用百度翻譯API實(shí)現(xiàn)一個(gè)翻譯功能
一直喜歡用Google Translate API進(jìn)行在線(xiàn)翻譯,但是服務(wù)越來(lái)越慢這篇文章,所以只能換一個(gè)了,主要給大家介紹了關(guān)于C#調(diào)用百度翻譯API實(shí)現(xiàn)一個(gè)翻譯功能的相關(guān)資料,需要的朋友可以參考下2021-06-06

