實(shí)例講解C#中的職責(zé)鏈模式
大家好,歡迎來到老胡的博客,今天我們繼續(xù)了解設(shè)計模式中的職責(zé)鏈模式,這是一個比較簡單的模式。跟往常一樣,我們還是從一個真實(shí)世界的例子入手,這樣大家也對這個模式的應(yīng)用場景有更深刻的理解。
一個真實(shí)的栗子
作為上班族,相信大家對請假都不陌生,每個公司都有自己請假的流程,稍微講究點(diǎn)的公司還會有細(xì)致的規(guī)定,比如,3天以內(nèi)的假期,小組長有權(quán)力批準(zhǔn),3天以上的假期就要找更高級別的領(lǐng)導(dǎo)批準(zhǔn)。這種制度就是典型的權(quán)力越大職責(zé)越大——畢竟,批長假的職責(zé)只在高級主管那里存在。
除了規(guī)定出這樣細(xì)致的要求之外,大部分公司還有用軟件實(shí)現(xiàn)了請假流程,當(dāng)請假人員提出請假申請的時候,會依據(jù)請假天數(shù),轉(zhuǎn)發(fā)給具有權(quán)限的人員審批,讓我們看看這個系統(tǒng)的代碼實(shí)現(xiàn)吧。
請假系統(tǒng)實(shí)現(xiàn)
在這個系統(tǒng)中,我們假定:
- 小組長可以審批3天以內(nèi)的請假請求
- 部門經(jīng)理可以審批5天以內(nèi)的請假請求
- 10天以內(nèi)的請假請求只有老板才能審批
- 我們同時假定,這個公司的管理層非常人性化,請假都能得到批準(zhǔn),除非大于10天,因為這種情況沒人可以審批
請假申請
這是最簡單的類,封裝了請假天數(shù)和請假申請人
class VacationRequest { public int DayNum { get; set; } public string RequesterName { get; set; } }
假期審批者
首先創(chuàng)建一個抽象類,假期審批者,封裝假期審批的基本邏輯,即,如果當(dāng)前人員有權(quán)限審批當(dāng)前假期申請,就處理
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); }
當(dāng)然,抽象類只需要確定算法骨架,限定只有當(dāng)前人員能處理這個請求的時候,才進(jìn)行審批工作,至于具體的審批實(shí)現(xiàn),留給子類自己去覆蓋,這種在父類固定算法骨架,暴露部分覆蓋點(diǎn)給子類的做法,就是之前我們提到過的TemplateMethod模式
具體假期審批者
小組長,部門經(jīng)理,老板,都在這里創(chuàng)建,他們分別處理能審批3、5、10天的請假申請
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"); } }
請假審批系統(tǒng)
請假審批系統(tǒng)提供統(tǒng)一請假申請接口,內(nèi)部通過請假天數(shù)決定哪個審批者參與審批
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"); } } }
測試代碼
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" }); } }
結(jié)果顯示
一切都是正常的,當(dāng)5天時,部門經(jīng)理審批,10天時,老板審批,大于10天無人能批。 Good job。
回頭看看
實(shí)現(xiàn)了第一版代碼之后,我們再回過頭看看,雖然代碼功能無誤,但是VacationApproveSystem似乎承擔(dān)了過多的職責(zé),它不但需要提供統(tǒng)一的請假審批接口給最終用戶,它同時還需要知道每個請假審批者能審批的請假天數(shù)并在內(nèi)部實(shí)現(xiàn)請假請求轉(zhuǎn)發(fā)給不同審批者的邏輯。這樣既違反了迪米特法則——它知道的太多了,也違反了開閉原則——如果任何一個審批者修改了自身能審批的請假天數(shù),這個類都會被波及,最后,它還違反了單一職責(zé)——一個類只能有一個引起變化的原因。
有鑒于此,我們這版代碼只能算湊合用,但遠(yuǎn)遠(yuǎn)談不上結(jié)構(gòu)良好,老老實(shí)實(shí)地重構(gòu)代碼吧,下面請出我們今天的主角。
職責(zé)鏈模式
解耦具體對象和請求,使得多個對象都有機(jī)會處理請求。將對象連成一條鏈,沿著鏈傳遞請求直到有對象處理它
乍一聽有點(diǎn)生澀,翻譯一下就是
- 解耦具體對象和請求——不要預(yù)先指定哪個對象來處理此請求(因為很多時候并不知道)
- 使多個對象都有機(jī)會——有一眾候選對象,具體使用哪個對象是在運(yùn)行時決定的
- 連成鏈傳遞請求——像鏈表一樣,要在對象中體現(xiàn)出對象之間的鏈關(guān)系,而不要通過其他類以if..else的方式實(shí)現(xiàn)
所以,這么看來這個模式和我們的例子簡直是絕配,我們已經(jīng)做了大部分的工作了,現(xiàn)在剩下的就只是修改審批者,讓審批者能鏈起來
代碼重構(gòu)
修改請假審批基類
最重要的改動,就是修改基類,讓對象能鏈起來,在VacationApprover中添加一個后繼節(jié)點(diǎn)和一個設(shè)置后繼節(jié)點(diǎn)的方法。同時在基類的審批方法中,完成請求傳遞,即,如果請假申請超過了當(dāng)前審批人的能力范圍,則轉(zhuǎn)發(fā)至后繼節(jié)點(diǎn)。修改后的類如下
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); }
修改請假審批系統(tǒng)
基類重構(gòu)結(jié)束之后,請假審批系統(tǒng)就可以瘦身了,刪除了所有判斷邏輯,僅僅在構(gòu)造函數(shù)里面完成鏈組建的工作,接著一鍵調(diào)用,齊活。
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); } }
測試
其他請假審批子類和測試客戶端都不需要改動,這次重構(gòu)工作量非常小,運(yùn)行代碼,一切正常,重構(gòu)成功。
總結(jié)
這就是職責(zé)鏈模式的使用。和狀態(tài)模式有點(diǎn)像,解決了以下問題:
- 通過添加子類把一些邏輯判斷從調(diào)用類(VaccationApproveSystem)移到子類的方式,使得調(diào)用類滿足迪米特法則
- 想在職責(zé)鏈上面添加更多節(jié)點(diǎn)的時候,只需要添加新類和修改鏈組裝部分的代碼,基本滿足開閉原則(這里幾乎不可能完全滿足開閉原則,畢竟有修改就意味著我們肯定會改動VaccationApproveSystem類,只是我們應(yīng)該盡量的讓代碼改動量少,以提高控制代碼變動的能力)
和狀態(tài)模式一樣,它也有子類爆炸的風(fēng)險。
可能有朋友會感到疑惑,既然職責(zé)鏈模式和狀態(tài)模式看起來那么像,那它們有什么區(qū)別呢?它們的區(qū)別在于:
- 狀態(tài)模式中的對象是有狀態(tài)的,可以隨時通過接口查詢對象的當(dāng)前狀態(tài),對象正是因為有了不同的狀態(tài),才會表現(xiàn)出不同行為。而職責(zé)鏈模式中的對象沒有狀態(tài),對象和鏈的關(guān)系更像請求和處理管線的關(guān)系,沒有接口能告訴我們當(dāng)前在處理管線的哪個節(jié)點(diǎn),也沒有意義這么做,我們只關(guān)心請求是否被處理了
- 狀態(tài)模式中的狀態(tài)切換可以是無序的,比如,一個游戲角色,當(dāng)他的狀態(tài)是虛弱的時候,可以通過治療,轉(zhuǎn)換成健康,也可以通過受傷轉(zhuǎn)換成瀕死。而職責(zé)鏈中的請求轉(zhuǎn)發(fā)就只有向前一條路,從小組長到部門經(jīng)理,從部門經(jīng)理到老板
根據(jù)不同的情景,選擇合適的模式,才是正確的使用之道。以上就是今天的內(nèi)容,希望大家喜歡,我們下次見!
以上就是實(shí)例講解C#中的職責(zé)鏈模式的詳細(xì)內(nèi)容,更多關(guān)于C# 職責(zé)鏈模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
WPF利用CommunityToolkit.Mvvm實(shí)現(xiàn)級聯(lián)選擇器
這篇文章主要介紹了WPF如何利用CommunityToolkit.Mvvm實(shí)現(xiàn)級聯(lián)選擇器,文中的示例代碼講解詳細(xì),對我們的學(xué)習(xí)或工作有一定幫助,需要的小伙伴可以參考一下2023-12-12C#將html table 導(dǎo)出成excel實(shí)例
C#將html table 導(dǎo)出成excel實(shí)例,需要的朋友可以參考一下2013-04-04ASP.NET總結(jié)C#中7種獲取當(dāng)前路徑的方法
本文主要介紹了7種獲取當(dāng)前路徑的方法,并做了代碼演示,分享給大家,感興趣的朋友可以參考一下。2016-03-03