[Design Pattern]命令模式(Command Pattern)

[Design Pattern]命令模式(Command Pattern)

 

Command Pattern其實就是封裝請求,然後進行一系列的參數化或操作..等,

下面針對需求我門來看看命令模式可以為程式碼帶來什麼樣的好處。

 

 

UML

image

 

需求:檢驗日期格式、下拉選單長度、描述內容是否含有Rico關鍵字

 

大多人可能會新增一個類別並包含所有驗證方法


 public class Validation
    {
        public Validation() { }

        public void YYYYMMDD(string indate) 
        {
            string pattern = @"[0-9]{4,4}/[0-9]{2,2}/[0-9]{2,2}";
            Match match = Regex.Match(indate, pattern, RegexOptions.IgnoreCase);
            if (match.Success)
                Console.WriteLine("Error:日期格式不符合YYYY/MM/dd");
            else
                Console.WriteLine("日期格式正確");
        }

        public void Length(string incontent)
        {
            if (incontent.Length > 50)
                Console.WriteLine("Error:內容長度不可大於50");
            else
                Console.WriteLine("長度正確");
        }

        public void KeyWord(string incontent)
        {
            if (!incontent.Contains("Rico"))
                Console.WriteLine("Error:內容要存在 Rico");
            else
                Console.WriteLine("內容正確");   
        }        
    
    }

 

 

 

Client端呼叫


//前端UI物件和內容
            Dictionary<string, string> uidict = new Dictionary<string, string>();
            uidict.Add("textbox", "2012/13/01");
            uidict.Add("drowdownlist", "123456789");
            uidict.Add("textarea", "I am RiCo");            
           
            Validation validation = new Validation();
            foreach (KeyValuePair<string, string> kv in uidict)
            {
                if (kv.Key == "textbox")
                    validation.YYYYMMDD(kv.Value);
                else if (kv.Key == "drowdownlist")
                    validation.Length(kv.Value);
                else if (kv.Key == "textarea")
                    validation.KeyWord(kv.Value);
            }    

 

執行結果

image

 

雖然執行結果沒有錯誤,且這樣撰寫程式很直覺也很簡單,

但你有沒有想過,如果今天驗證邏輯有30個呢(不要告訴我,只要持續改寫 if else or 新增 foreach 就好了...)?

而且Client端也需要具體知道使用那個方法,

這樣 Client 端和執行檢驗類別就呈現了一種高耦合(需要判斷那種資料要執行那一種方法),

大家一定都知道寫程式一定要盡力達到高內聚、低耦合,

所以Client應該只要執行檢驗命令並輸入UI資料就好,

至於執行那種類型檢驗命令可以不用知道,

至於要如何達到呢?下面就來看看使用命令模式重構後的程式碼。

 

 

使用命令模式拆解每項驗證方法降低耦合度

Client端程式碼


//前端UI物件和內容
            Dictionary<string, string> uidict = new Dictionary<string, string>();
            uidict.Add("textbox", "2012/13/01");
            uidict.Add("drowdownlist", "123456789");
            uidict.Add("textarea", "I am RiCo");

            Validation validation = new Validation();
            ValidationCommand myyyyymmdd = new YYYYMMDDCommand(validation);
            (myyyyymmdd as YYYYMMDDCommand).InDate = uidict["textbox"].ToString();

            ValidationCommand mylength = new LengthCommand(validation);
            (mylength as LengthCommand).InValue = uidict["drowdownlist"].ToString();

            ValidationCommand mykeyword = new KeyWordCommand(validation);
            (mykeyword as KeyWordCommand).InValue = uidict["textarea"].ToString();

            //相關檢驗命令執行
            CommandInvoker invoker = new CommandInvoker();
            invoker.SetCommand(myyyyymmdd);
            invoker.SetCommand(mylength);
            invoker.SetCommand(mykeyword);
            invoker.ExecuteAll();  

 

 

新增一個抽象命令類別


/// <summary>
    /// 抽象命令(先對命令進行抽象)
    /// </summary>
    public abstract class ValidationCommand
    {
        protected Validation _validation;
        public ValidationCommand(Validation validation)
        {
            this._validation = validation;
        }

        /// <summary>
        /// 執行驗證命令
        /// </summary>     
        public abstract void Execute();    
    }

 

 

拆解每個命令並提高為類別層級且繼承抽象命令類別(共同的介面)

真正執行的檢驗邏輯


/// <summary>
    ///  日期格式檢驗命令
    /// </summary>
    public class YYYYMMDDCommand:ValidationCommand
    {
        private string _indate;
        public string InDate
        {
            set { this._indate = value; }
            get { return this._indate; }
        }
        public YYYYMMDDCommand(Validation validation)
            : base(validation)
        { 
        
        }

        public override void Execute()
        {
            _validation.YYYYMMDD(this._indate);
        }    
    }

 

 


/// <summary>
    ///  長度檢驗命令
    /// </summary>
    public class LengthCommand : ValidationCommand
    {
        private string _invalue;
        public string InValue
        {
            set { this._invalue = value; }
            get { return this._invalue; }
        }
        public LengthCommand(Validation validation)
            : base(validation)
        { 
           
        }

        public override void Execute()
        {
            _validation.Length(_invalue);
        }
    }

 

 


 /// <summary>
    /// 關鍵字檢驗命令
    /// </summary>
    public class KeyWordCommand : ValidationCommand
    {
        private string _invalue;
        public string InValue
        {
            set { this._invalue = value; }
            get { return this._invalue; }
        }
        public KeyWordCommand(Validation validation)
            : base(validation)
        { 
        
        }

        public override void Execute()
        {
            _validation.KeyWord(_invalue);
        }
    }   

 

中間包一層命令執行類別


/// <summary>
    /// 執行者(反正都是命令,只需紀錄命令然後執行)
    /// </summary>
    public class CommandInvoker
    {
        List<ValidationCommand> commands = new List<ValidationCommand>();

        public void SetCommand(ValidationCommand command)
        {
            commands.Add(command);        
        }

        public void RemoveCommand(ValidationCommand command)
        {
            commands.Remove(command);
        }

        public void ExecuteAll()
        {
            foreach (ValidationCommand command in commands)
            {
                command.Execute();
            }
        }

        public void Execute(ValidationCommand incommand)
        {
            foreach (ValidationCommand command in commands)
            {
                if (incommand.Equals(command))
                {
                    command.Execute();
                    break;
                }                   
            }
        }

    }

 

 

 

雖然重構後的類別變多了,但命令模式可讓你的程式碼更具靈活性,

往後也可以很容易擴充或組合相關執行命令,並且不影響之前已通過測試的程式碼,

而最大的優點就是把Client(請求者)和如何執行(執行者)分離,

算是一個很好分離UI和商業邏輯的設計模式,不過由於拆解命令並封裝為類別層級,

所以當命令過多時,無法避免類別爆炸的問題,實際應用上還得多加評估。

 

 

參考

Command pattern