Design Pattern (G4) - Chain of Responsibility

一般我們在處理資料時,如果有多種類型資料就要判斷資料類型來決定處理的方式,最簡單的方式就是用一堆if...else,但這樣不僅看起來很醜,而且邏輯一變動就要改一拖拉庫。這時候就可以使用Chain of Responsibility,讓處理方法自己決定是否要處理,由被動變成主動,增加程式的獨立性。

目的

為了降低Client與Handler之間的Coupling,不用特別指定哪個Handler處理,由Handler決定是否要處理。

動機

在程式需要根據資料類型進行不同作業的情況下,一般來說Client會利用if...else或switch來指定特定的Handler處理,這樣是會造成Client與Handler Coupling太高,如果要增加或減少Handler或改變Handler邏輯時就比較麻煩。

使用時機


  1. 當你有多個Handler,要在執行期間各種處理方式自己決定是否要執行。
  2. 當你發出一個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。

優點


  1. 可降低Client與各Handler之間的Coupling。
  2. 可動態增加或減少Handler,增加了彈性。

相關樣式

  1. Composite

使用頻率











參考來源:
Design Patterns - Gang of Four
dofactory - Chain of Responsibility

留言