# 14. Chain of Responsibility ###### tags: `DesignPatterns` ## 關於 Chain of Responsibility 本篇將討論以下幾個問題 > ### 1. 關於 Chain of Responsibility > ### 2. UML > ### 3. 將 UML 轉為程式碼 > ### 4. 情境 --- ## 測試環境: >OS:Windows 10 >IDE:Visual Studio 2019 --- ## 1. 關於 Chain of Responsibility > Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. > > by [Gang of Four](https://en.wikipedia.org/wiki/Design_Patterns) - 藉由給一個以上對像處理請求的機會,避免將請求的發送者與接收者耦合 - 鏈接接收對象,並將請求沿著鏈傳遞,直到對象處理該請求 Chain of Responsibility(責任鏈)屬於行為型(Behavioral Patterns),當遇到**未知請求 & 未知處理者**時,可使用 Chain of Responsibility 來將請求依序對應全部處理者,找尋可處理請求的對象。當遇到**過濾、攔截**的需求時,可使用 Chain of Responsibility 來依照指定順序執行判斷邏輯。 優點: - 符合 單一職責原則(Single Responsibility Principle) - 符合 開閉原則(Open Closed Principle) 缺點: - 用於未知請求 & 未知處理者時,可能會因為處理者過多而影響效能 --- ## 2. UML  Class 間關聯: - Client 關聯 Handler - ConcreteHandler 1 / 2 繼承 Handler - ConcreteHandler2 可包含 Handler Class: - Client:呼叫端 - Handler:處理請求的介面 - ConcreteHandler:處理請求實作 --- ## 3. 將 UML 轉為程式碼 處理請求的抽象類別 ```C# /// <summary> /// 處理請求的抽象類別 /// </summary> public abstract class Handler { protected Handler successor; public void SetSuccessor(Handler successor) { this.successor = successor; } public abstract void HandleRequest(int request); } ``` 處理請求實作 1 / 2 / 3 ```C# /// <summary> /// 處理請求實作 /// </summary> public class ConcreteHandler1 : Handler { public override void HandleRequest(int request) { if (request >= 0 && request < 10) { Console.WriteLine($"{GetType().Name} handled request {request}"); } else if (successor != null) { successor.HandleRequest(request); } } } /// <summary> /// 處理請求實作 /// </summary> public class ConcreteHandler2 : Handler { public override void HandleRequest(int request) { if (request >= 10 && request < 20) { Console.WriteLine($"{GetType().Name} handled request {request}"); } else if (successor != null) { successor.HandleRequest(request); } } } /// <summary> /// 處理請求實作 /// </summary> public class ConcreteHandler3 : Handler { public override void HandleRequest(int request) { if (request >= 20 && request < 30) { Console.WriteLine($"{GetType().Name} handled request {request}"); } else if (successor != null) { successor.HandleRequest(request); } } } ``` 1. 建立`handler` 2. 經由`SetSuccessor`設置接續執行的`handler` 3. 依序傳入測試資料 ```C# static void Main(string[] args) { Default.Handler handler1 = new Default.ConcreteHandler1(); Default.Handler handler2 = new Default.ConcreteHandler2(); Default.Handler handler3 = new Default.ConcreteHandler3(); handler1.SetSuccessor(handler2); handler2.SetSuccessor(handler3); var requests = new[]{ 5, 12, 24 }; Console.WriteLine("Array elements: 5, 12, 24"); foreach (var request in requests) { handler1.HandleRequest(request); } Console.ReadLine(); } ``` 執行結果 ```Console Array elements: 5, 12, 24 ConcreteHandler1 handled request 5 ConcreteHandler2 handled request 12 ConcreteHandler3 handled request 24 ``` --- ## 4. 情境 我們接到了一個員工請假簽核的需求 - 三天內(包含三天)**店長**同意即可 - 三到七天(包含七天)**店長 & 區主管**同意 - 超過七天則要**店長 & 區主管 & 總經理**同意 - 假單需依序簽核 請假簽核抽象類別 ```C# /// <summary> /// 請假簽核抽象類別 /// </summary> public abstract class Leave { protected Leave successor; public void SetSuccessor(Leave successor) { this.successor = successor; } public abstract void TakeALeave(int days); } ``` 店長 & 區主管 & 總經理簽核實作 ```C# /// <summary> /// 店長簽核實作 /// </summary> public class StoreManagerApprove : Leave { public override void TakeALeave(int days) { if (days <= 3) { Console.WriteLine($"Store Manager : OK~"); } else if (successor != null) { Console.WriteLine($"Store Manager : OK~"); successor.TakeALeave(days); } } } /// <summary> /// 區主管簽核實作 /// </summary> public class DistrictManagerApprove : Leave { public override void TakeALeave(int days) { if (days <= 7) { Console.WriteLine($"District Manager : OK~"); } else if (successor != null) { Console.WriteLine($"District Manager : OK~"); successor.TakeALeave(days); } } } /// <summary> /// 總經理簽核實作 /// </summary> public class GeneralManagerApprove : Leave { public override void TakeALeave(int days) { if (days > 7) { Console.WriteLine($"General Manager : OK~"); } } } ``` 1. 建立請假流程`handler` 2. 經由`SetSuccessor`設置接續執行的主管 3. 依序傳入請假天數(1、5、10 天) ```C# static void Main(string[] args) { Situation.StoreManagerApprove storeManager = new Situation.StoreManagerApprove(); Situation.DistrictManagerApprove districtManager = new Situation.DistrictManagerApprove(); Situation.GeneralManagerApprove generalManager = new Situation.GeneralManagerApprove(); storeManager.SetSuccessor(districtManager); districtManager.SetSuccessor(generalManager); Console.WriteLine($"請假 1 天"); storeManager.TakeALeave(1); Console.WriteLine($"\n"); Console.WriteLine($"請假 5 天"); storeManager.TakeALeave(5); Console.WriteLine($"\n"); Console.WriteLine($"請假 10 天"); storeManager.TakeALeave(10); Console.WriteLine($"\n"); Console.ReadLine(); } ``` 執行結果 ```Console 請假 1 天 Store Manager : OK~ 請假 5 天 Store Manager : OK~ District Manager : OK~ 請假 10 天 Store Manager : OK~ District Manager : OK~ General Manager : OK~ ``` --- ## 完整程式碼 GitHub:[Behavioral_01_ChainOfResponsibility](https://github.com/darionnnnnn/blog/tree/master/Blog/Behavioral_01_ChainOfResponsibility) --- ## 總結 ### 本篇沒有將 abstract class 改為 interface,同樣是因為覺得 Chain of Responsibility 使用繼承的方式能使子類別更加簡潔,將接續處理的動作放在父類別,子類別只專注在處理邏輯上與決定是否需要呼叫接續者繼續執行。 --- ## 參考資料 1. [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) 2. [大話設計模式](https://www.tenlong.com.tw/products/9789866761799) 2. [dofactory](https://www.dofactory.com/) 3. [Refactoring.Guru](https://refactoring.guru/) --- ## 新手上路,若有錯誤還請告知,謝謝
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up