Chain of Resposibility 責任鍊模式

實作責任鍊模式

上課時Bill老師講到責任鍊模式用以封裝if判斷邏輯,心裡想說奇怪了 怎麼會對這個模式完全沒有印象,封裝判斷邏輯只會狀態跟策略模式,回家翻書一看,恩...書上有,一定是之前偷懶沒有實作練習,所以完全沒有印象.......orz

責任鍊模式很像公文系統, 你遞公文上去,每層的主管都會檢查內容是否有誤,內容無誤的話依據公文等級是否需要再往上層遞,直到最後一個權責主管後簽呈結束

    abstract class 權責主管
    {
        protected abstract bool 檢查公文內容是正確的(string 公文);
        protected 權責主管 下一位權責主管;

        public 權責主管 指定下一位權責主管(權責主管 checker) {
            this.下一位權責主管 = checker;
            return this.下一位權責主管;
        }

        public bool 檢查(string 公文) {
            if (檢查公文內容是正確的(公文)) {
                if (下一位權責主管 != null) {
                    return 下一位權責主管.檢查(公文);
                } else {
                    return true;
                }
            } else {
                return false;
            }
        }
    }

來看一下從網路上找到的class diagram

http://fox.wikis.com/graphics/chain094.gifhttp://fox.wikis.com/graphics/chain094.gif

運作上的流程圖

http://fox.wikis.com/graphics/chain089.gifhttp://fox.wikis.com/graphics/chain089.gif

真的很像linked list,  不過責任鍊不一定是單鍊 , 仍可能有其他變形

從上面的圖來看 每個Handler需要

  • 屬於自己業務範圍的判斷邏輯
  • 判斷是否需要遞交給下一個Handler(sucessor)
  • 指定下一個Handler(sucessor)的能力

以檢查一個長方體的長寬高和體積, 實作一般if else以及使用責任鍊為例

長方體的class

    class Cuboid
    {
        public int Length { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public int Volume { get { return this.Length * this.Width * this.Height; } }
    }

責任鍊的抽象類別, 使用set public來指定下一個successor(checker),  也可以使用constructor或是delegate實作(Bill老師給的delegate例子真的是簡潔漂亮)

    abstract class AbstractChecker
    {
        protected abstract bool InternalCheck(Cuboid cuboid);
        protected AbstractChecker _nextChecker;

        public AbstractChecker setNextChecker(AbstractChecker checker) {
            this._nextChecker = checker;
            return this._nextChecker;
        }

        public bool Check(Cuboid cuboid) {
            if (InternalCheck(cuboid)) {
                if (_nextChecker != null) {
                    return _nextChecker.Check(cuboid);
                } else {
                    return true;
                }
            } else {
                Console.WriteLine($"{this.GetType().Name}:false");
                return false;
            }
        }
    }

實作責任鍊類別

    internal class LengthChecker : AbstractChecker
    {
        protected override bool InternalCheck(Cuboid cuboid) {
            return cuboid.Length > 15;
        }
    }

    internal class WidthChecker: AbstractChecker
    {
        protected override bool InternalCheck(Cuboid cuboid) {
            return cuboid.Width > 10;
        }
    }

    internal class HeightChecker : AbstractChecker
    {
        protected override bool InternalCheck(Cuboid cuboid) {
            return cuboid.Height > 5;
        }
    }

    internal class VolumeChecker: AbstractChecker
    {
        protected override bool InternalCheck(Cuboid cuboid) {
            return cuboid.Volume < 100;
        }
    }

事前準備完成後, 我們來比較一下使用一般if else跟使用責任鍊的差別

使用一般if else

        public static bool CheckCuboid(Cuboid cuboid) {
            if (CheckLength(cuboid)) {
                if (CheckWidth(cuboid)) {
                    if (CheckHeight(cuboid)) {
                        if (CheckVolume(cuboid)) {
                            return true;
                        } else {
                            return false;
                        }
                    } else {
                        return false;
                    }
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }

使用責任鍊

        public static bool CheckCuboidByCOR(Cuboid cuboid) {
            AbstractChecker checker = new LengthChecker();
            checker.setNextChecker(new WidthChecker())
                   .setNextChecker(new HeightChecker())
                   .setNextChecker(new VolumeChecker());

            return checker.Check(cuboid);
        }

雖然責任鍊的事前準備多了一些, 但是判斷的先後邏輯相當的清晰, 如需擴充更多判斷邏輯也不用加長if else, 

更棒的是  如果需要作通用的額外處理,像是Console輸出哪裡判斷false

我可以在抽象類別裡面只要加一行Console.WriteLine($"{this.GetType().Name}:false") 即可得知在哪個實作類別中止並丟出false,

而傳統if else則需要在每一層都手動加入 Console。 在if else多層的情況下,責任鍊是個重構的好選擇