Gof定義
將一個請求封裝爲一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可撤銷的操作。
動機
在軟件構建過程中,“行爲請求者”與“行爲實現者”通常呈現一種“緊耦合”。但在某些場合——比如需
要對行爲進行“記錄、撤銷/重做(undo/redo)、事務”等處理,這種無法抵禦變化的緊耦合是不合適的。在這種情況下,如何將“行爲請求者”與“行爲實現者”解耦?將一組行爲抽象爲對象,可以實現二者之間的鬆耦合。
先來看個反例(版本1),假設在一個應用程序中需要用到要用到很多的一些外部的類,並且要對這些類中的操作進行撤銷、記錄等操作,如果像下面這樣實現就會很亂並且不容易實現:
public class Application { public void Process() { Document doc = new Document(); doc.ShowText(); Graphics gra = new Graphics(); gra.Draw(); //需要進行undo 記錄等操作 } } public class Document { public void ShowText() { } } public class Graphics { public void Draw(){ } }
要滿足上面提的那些要求就需要用命令模式,命令模式結構圖如下:
改進後的代碼(版本2),將ShowText Draw這種行爲抽象起來放到一個接口中,接口命名爲ICommand,並且在該接口中還有一個方法簽名Undo,用來做撤銷處理,當然根據需要還可以加其他的操作。ICommand代碼如下:
public interface ICommand { void Execute(); void Undo(); }
Document類和Graphics類實現該接口,並提供自己的實現,代碼如下:
public class Document:ICommand { public string Name { get;set;} public Document(string name) { Name = name; } public void Execute() { Console.WriteLine("顯示文本 "+Name); } public void Undo() { Console.WriteLine("撤銷顯示文本 "+Name); } } public class Graphics : ICommand { public string Name { get;set;} public Graphics(string name) { Name = name; } public void Execute() { Console.WriteLine("畫圖 "+Name); } public void Undo() { Console.WriteLine("撤銷畫圖 "+Name); } }
客戶端調用的代碼:
public class Application { public Stack<ICommand> stack=new Stack<ICommand>(); public void Show() { foreach (ICommand cmd in stack) { cmd.Execute(); } } public void Undo() { ICommand command = stack.Pop(); command.Undo(); } }
static void Main(string[] args) { Application app = new Application(); app.stack.Push(new Document("1")); app.stack.Push(new Graphics("1")); app.stack.Push(new Document("2")); app.stack.Push(new Graphics("2")); app.Show(); app.Undo(); Console.ReadLine(); }
上面的代碼運行結果如下:
從結果中可以看出,最後執行的畫圖2 被撤銷了。
在上面的代碼中,像Document、Graphics這樣的類都是實現了ICommand接口,如果項目中已經存在這樣的類然後還要將這些類去實現ICommand接口,顯然不是很合理,那麼就需要添加DocumentCommand類來進行轉化。完整代碼如下(版本3):
public class Document { public void ShowText() { Console.WriteLine("顯示文本 "); } } public class Graphics { public void Draw() { Console.WriteLine("畫圖 "); } } public interface ICommand { void Execute(); void Undo(); } /// <summary> /// 具體化的文檔命令類 /// </summary> public class DocumentCommand : ICommand { Document _doc; public DocumentCommand(Document doc) { _doc = doc; } public void Execute() { _doc.ShowText(); } public void Undo() { Console.WriteLine("撤銷顯示文本 "); } } /// <summary> /// 具體化的圖像命令類 /// </summary> public class GraphicsCommand : ICommand { Graphics _gra; public GraphicsCommand(Graphics gra) { _gra = gra; } public void Execute() { _gra.Draw(); } public void Undo() { Console.WriteLine("撤銷畫圖 "); } } public class Application { public Stack<ICommand> stack = new Stack<ICommand>(); public void Show() { foreach (ICommand cmd in stack) { cmd.Execute(); } } public void Undo() { ICommand command = stack.Pop(); command.Undo(); } } class Program { static void Main(string[] args) { Application app = new Application(); app.stack.Push(new DocumentCommand(new Document())); app.stack.Push(new GraphicsCommand(new Graphics())); app.Show(); app.Undo(); Console.ReadLine(); } }
在版本1中Application類中直接和Document中的方法相耦合,進過一步步改進後,Application類只和Document和Graphics的抽象耦合,達到了解耦的目的。
Command模式的幾個要點
- Command模式的根本目的在於將“行爲請求者”與“行爲實現者” 解耦,在面嚮對象語言中,常見的實現手段是“將行爲抽象爲對象”。
- 實現Command接口的具體命令對象ConcreteCommand有時候根據需要可能會保存一些額外的狀態信息。
- 通過使用Composite模式,可以將多個“命令”封裝爲一個“複合命令”MacroCommand。
- Command模式與C#中的Delegate有些類似。但兩者定義行爲接口的規範有所區別:Command以面向對象中的“接口-實現”來定義行爲接口規範,更嚴格,更符合抽象原則;Delegate以函數簽名來定義行爲接口規範,更靈活,但抽象能力比較弱。