續剝殼模式 -- 改變為可擴展的設計

承前一篇文--剝殼模式談到了剝殼模式完全倚賴繼承鏈的作法會有難以擴展(的缺點, 後來我就一直想如果情境變成需要擴展該怎麼辦 ? 這問題優先要解決的就是打破繼承, 讓各層命令間不再有繼承的關係

        承前一篇文--剝殼模式談到了剝殼模式完全倚賴繼承鏈的作法會有難以擴展的缺點, 後來我就一直想如果情境變成需要擴展該怎麼辦 ? 這問題優先要解決的就是打破繼承, 讓各層命令間不再有繼承的關係. 於是限制條件現在變成了三個:

 

         (1) 在編輯時期使用方法鏈串接不同的命令

         (2) 命令的順序限制為以下各種形式 (L1 代表第一層命令, L2 代表第二層命令, L3 代表第三層命令, Result 代表取得最終結果的命令)

            L1 => Result

            L1 => L2 => Result
            L1 => L2  => L3 => Result

            L1 => L3 => Result

             L2 => Result

            L2  => L3 => Result

            L3 => Result

         (3) 要可以擴展

 

 

        好啦, 思前想後, 甚麼光怪陸離, 稀奇古怪的想法都試了一試, 結果是找不出來要怎麼樣才能符合這三個條件, 但我想不出來不代表真的沒有, 只是我想不出來而已, 如果你有做出可以符合這三個條件的做法, 麻煩告訴我一下, 在此先行謝過.

 

 

        通常事情是這樣的, 如果所有的條件無法同時達成,  就得要犧牲某些條件, 條件二是一個很重要的條件, 沒有辦法消除; 由於這回假設未來擴充的可能性很高, 於是第三個條件也不能犧牲. 那結果很明顯, 第一個條件的需求度可能低一點, 只好犧牲它了(請各位為條件一默哀三秒).

 

 

        既然需求與限制條件改變, 設計也必須跟著改變, 這次的解法採用 Enum + Attribute + Reflection 來解決這問題. 因為需求上只有傳出字串, 所以直接使用 .Net Framework 內建的 DescriptionAttribute; 如果有特殊的需求, 就可能會需要自己設計一個 Attribute.

 

 

        第一個步驟, 為每個命令層建立列舉, 並在各列舉值上加上 attribute, 而且必須有 None 的設計, 表示使用 None 值時回傳空字串 :


 public enum CommandL3
    {
        [DescriptionAttribute("")]
        None,
        [DescriptionAttribute("探病")]
        AAA,
        [DescriptionAttribute("讀書")]
        BBB,
        [DescriptionAttribute("購物")]
        CCC

    }

    public enum CommandL2
    {
        [DescriptionAttribute("")]
        None,
        [DescriptionAttribute("去醫院")]
        AA,
        [DescriptionAttribute("去學校")]
        BB,
        [DescriptionAttribute("去商店")]
        CC
    }

    public enum CommandL1
    {
        [DescriptionAttribute("")]
        None,
        [DescriptionAttribute("我")]
        A,
        [DescriptionAttribute("你")]
        B,
        [DescriptionAttribute("他")]
        C
    }

 

 

        接著為取得結果這個職責設計一個 Interface, 並建立一個實作此 Interface 的類別, 在類別中加上一個擁有三個參數 (型別分別為剛剛建立的命令層列舉型別) 建構式.


 public interface ICommandResult
    {
        string Result
        { get; }
    }

    public class CommandResult : ICommandResult
    {
        private string result;
        public string Result
        {
            get { return result; }
        }

        public CommandResult(CommandL1 c1, CommandL2 c2, CommandL3 c3)
        {
            result = GetDescription(c1) + GetDescription(c2) + GetDescription(c3);
        }


        private string GetDescription(Enum e)
        {
            DescriptionAttribute attribute = e.GetType().GetField(e.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false)[0] as DescriptionAttribute;
            if (attribute != null)
            {
                return attribute.Description;
            }
            else
            {
                return string.Empty;
            }
        }
    }

 

 

        然後就可以在客戶端這樣使用 :


       static void Main(string[] args)
        {
            string result = new CommandResult(CommandL1.A, CommandL2.AA, CommandL3.BBB).Result;
            Console.WriteLine(result);

            result = new CommandResult(CommandL1.None , CommandL2.CC, CommandL3.AAA).Result;
            Console.WriteLine(result);

            Console.ReadLine();
        }

 

 

        現在的做法打破了各個命令間的相依關係, 當命令層次改變或是需要擴展時只要直接增加新的列舉, 然後實作 ICommandResult 介面建立一個新的類別即可. 如果你覺得一直實作那個反射的動作很煩, 也可以在中間墊一層抽象類別, 或乾脆把那個職責再抽取出來獨立.

 

 

        這是一個非常有趣的課題, 實作出來的程式碼是一回事, 它帶給我最重要的啟示是『如何在需求與條件的取捨下, 找出最佳的方案』.