文章目錄
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以函數簽名來定義行為介面規範,更靈活,但抽象能力比較弱。
返回開篇(索引)