[Design Pattern]命令模式(Command Pattern)
Command Pattern其實就是封裝請求,然後進行一系列的參數化或操作..等,
下面針對需求我門來看看命令模式可以為程式碼帶來什麼樣的好處。
UML
需求:檢驗日期格式、下拉選單長度、描述內容是否含有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);
}
執行結果
雖然執行結果沒有錯誤,且這樣撰寫程式很直覺也很簡單,
但你有沒有想過,如果今天驗證邏輯有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和商業邏輯的設計模式,不過由於拆解命令並封裝為類別層級,
所以當命令過多時,無法避免類別爆炸的問題,實際應用上還得多加評估。
參考