命令模式是對一類對象公共操做的抽象,它們具備相同的方法簽名,因此具備相似操做,能夠被抽象出來,成爲一個抽象的「命令」對象。請求以命令的形式包裹在對象中,並傳給調用對象。調用者尋找能夠處理該命令的合適的對象,並把該命令傳給相應的對象,該對象執行命令。這樣實際操做的調用者就不是和一組對象打交道,它只須要依賴於這個「命令」對象的方法簽名,並根據這個操做簽名調用相關的方法。sql
GOF對命令模式描述爲:
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests,and support undoable operations...
— Design Patterns : Elements of Reusable Object-Oriented Software設計模式
UML類圖:
ide
代碼示例:函數
public interface ICommand { void Execute(); Receiver Receiver { set; } } public class Receiver { public string Name { get; private set; } public string Address { get; private set; } public void SetName() { this.Name = "Name"; } public void SetAddress() { this.Name = "Address"; } } public abstract class CommandBase : ICommand { public Receiver Receiver { set; get; } public abstract void Execute(); } public class SetAddressCommand : CommandBase { public override void Execute() { base.Receiver.SetName(); } } public class SetNameCommand : CommandBase { public override void Execute() { base.Receiver.SetAddress(); } } public class Invoker { private IList<ICommand> commands = new List<ICommand>(); public void AddCommand(ICommand command) { commands.Add(command); } public void Run() { foreach (ICommand command in commands) { command.Execute(); } } }
Client代碼:this
Receiver receiver = new Receiver(); ICommand command1 = new SetNameCommand(); ICommand command2 = new SetAddressCommand(); command1.Receiver = receiver; command2.Receiver = receiver; Invoker invoker = new Invoker(); invoker.AddCommand(command1); invoker.AddCommand(command2); invoker.Run();
再來看看如何用命令模式實現Redo和Undo,要實現Redo和Undo就須要保存執行過的命令,並經過安排這些命令的執行順序來達到目地。
以SQL的執行爲例,下面的代碼定義了SQLExecute做爲Receiver,CommandManager做爲Invoker,InsertIntoCommand做爲ConcreteCommand:設計
public interface ICommand { public void Execute(); public void Undo(); } public class SQLExcute { public void InsertInto(string id) { Console.WriteLine("插入一條數據,id:" + id); } public void Delete(string id) { Console.WriteLine("刪除一條數據,id:" + id); } } public class InsertIntoCommand : ICommand { private SQLExcute sqlExcute; private string id; public InsertIntoCommand(SQLExcute sqlExcute, string id) { this.sqlExcute = sqlExcute; this.id = id; } public void Execute() { sqlExcute.InsertInto(id); } public void Undo() { sqlExcute.Delete(id); } } public class CommandManager { private Stack<ICommand> undoStacks = new Stack<ICommand>(); private Stack<ICommand> redoStacks = new Stack<ICommand>(); public void Execute(ICommand command) { command.Execute(); undoStacks.Push(command); if (redoStacks.Count > 0) { redoStacks.Clear(); } } public void Undo() { if (undoStacks.Count > 0) { ICommand pop = undoStacks.Pop(); pop.Undo(); redoStacks.Push(pop); } } public void Redo() { if (redoStacks.Count > 0) { ICommand pop = redoStacks.Pop(); pop.Execute(); } } }
Client代碼:指針
CommandManager manager = new CommandManager(); SQLExcute excute = new SQLExcute(); InsertIntoCommand command1 = new InsertIntoCommand(excute, "1"); InsertIntoCommand command2 = new InsertIntoCommand(excute, "2"); manager.Execute(command1); manager.Execute(command2); Console.WriteLine("undo------------"); manager.Undo(); manager.Undo(); Console.WriteLine("redo------------"); manager.Redo(); manager.Redo();
運行結果:日誌
插入一條數據,id:1 插入一條數據,id:2 undo------------ 刪除一條數據,id:2 刪除一條數據,id:1 redo------------ 插入一條數據,id:1 插入一條數據,id:2
能夠看到使用命令模式,調用者並不須要直接與實際的執行者打交道,實現了二者的解耦,此外基於命令的機制,能夠方便地作一些相似Undo, Redo的擴展,具體的優勢有:
優勢:code
缺點:
命令模式也有其固有的缺點:在命令擴充至較多的數量時,便須要建立對應數量的ConcreteCommand,命令類過多,系統的維護會比較複雜。對象
參考書籍: 王翔著 《設計模式——基於C#的工程化實現及擴展》