一般我們在處理資料時,如果有多種類型資料就要判斷資料類型來決定處理的方式,最簡單的方式就是用一堆if...else,但這樣不僅看起來很醜,而且邏輯一變動就要改一拖拉庫。這時候就可以使用Chain of Responsibility,讓處理方法自己決定是否要處理,由被動變成主動,增加程式的獨立性。
目的
為了降低Client與Handler之間的Coupling,不用特別指定哪個Handler處理,由Handler決定是否要處理。動機
在程式需要根據資料類型進行不同作業的情況下,一般來說Client會利用if...else或switch來指定特定的Handler處理,這樣是會造成Client與Handler Coupling太高,如果要增加或減少Handler或改變Handler邏輯時就比較麻煩。使用時機
- 當你有多個Handler,要在執行期間各種處理方式自己決定是否要執行。
- 當你發出一個Request,而不需要指定特定的Handler。
結構
Chain of Responsibility主要精隨為一個抽象Handler類別,這個抽象Handler定義了一個HandleRequest方法,各具象類別須根據自身邏輯來實作這個方法。另外,抽象Handler內要包含一個抽象Handler的屬性(successor),作為下一個傳遞的對象。Chain of Responsibility執行流程為為,先產生第一個具象Handler實體,然後依優先順續排序傳入下一個具象Handler的實體,以此類推。每個Handler在執行HandleRequest時先判斷自己能不能處理,如果不能就傳給下一個(successor),直到找到適合的Handler或都沒找到。
實作
限量在下面以一個檔案格式Parser的範例來說明Chain of Responsibility,如上圖。因為檔案有許多種格式,要取出其資料必須先按照各種格式的規範來解析檔案,像XML, JSON, CSV格式盡不相同,在未知的檔案格式的情況下,必須要提供多種格式的Parser,再根據進來檔案的格式來決定使用哪個Parser。這種情境就可以使用到Chain of Responsibility,我們可以先將各種可能用到的Parser利用Chain of Responsibility的特性串接起來,執行時會一層一層的串遞,各Parser接到檔案判斷自己是否能處理再處理,如果不行在傳給下一層的Parser,以此類推。 以下為範例程式碼:
using System; using System.IO; namespace TestSolution { class Program { static void Main(string[] args) { // 產生Parser實體,在按照優先順序加入其他Parser實體 var parser = new XMLFileParser( new JSONFileParser( new CSVFileParser(null) ) ); var dir = new DirectoryInfo("./files"); foreach(var file in dir.GetFiles()) { // 針對每個檔案Parse var result = parser.Parse(file); Console.WriteLine( string.Format("FileName: {0}, Result: {1}", file.Name, result) ); } Console.Read(); } } ////// 抽象類別,即Handler /// public abstract class FileParser { protected FileParser _successor; public FileParser(FileParser parser) { _successor = parser; } public abstract string Parse(FileInfo file); } ////// 具象類別,針對XML格式檔案,即ConcreteHandler /// public class XMLFileParser : FileParser { public XMLFileParser(FileParser parser) : base(parser) { } public override string Parse(FileInfo file) { var result = string.Empty; if (file.Extension.Equals(".xml", StringComparison.OrdinalIgnoreCase)) { // 解析XML result = "XML Result"; } else if (_successor == null) { result = "No suitable parser"; } else { result = _successor.Parse(file); } return result; } } ////// 具象類別,針對JSON格式檔案,即ConcreteHandler /// public class JSONFileParser : FileParser { public JSONFileParser(FileParser parser) : base(parser) { } public override string Parse(FileInfo file) { var result = string.Empty; if (file.Extension.Equals(".json", StringComparison.OrdinalIgnoreCase)) { // 解析JSON result = "JSON Result"; } else if (_successor == null) { result = "No suitable parser"; } else { result = _successor.Parse(file); } return result; } } ////// 具象類別,針對CSV格式檔案,即ConcreteHandler /// public class CSVFileParser : FileParser { public CSVFileParser(FileParser parser) : base(parser) { } public override string Parse(FileInfo file) { var result = string.Empty; if (file.Extension.Equals(".csv", StringComparison.OrdinalIgnoreCase)) { // 解析CSV result = "CSV Result"; } else if (_successor == null) { result = "No suitable parser"; } else { result = _successor.Parse(file); } return result; } } }
從上面執行結果可以看到,程式依序讀取V.html, W.csv, X.json, Z.xml檔案並Parse,因為沒有實作HTML的Parser,所以V.html檔案沒有Parse到,而其他檔案都有找到對應的Parser。
優點
- 可降低Client與各Handler之間的Coupling。
- 可動態增加或減少Handler,增加了彈性。
相關樣式
使用頻率
參考來源:
Design Patterns - Gang of Four
dofactory - Chain of Responsibility
留言
張貼留言