c#設計模式系列:命令模式(Command Pattern)
引言
命令模式,我感覺“命令”就是任務,執行了命令就完成了一個任務。或者說,命令是任務,我們再從這個名字上並不知道命令的發出者和接受者分別是誰,為什麽呢?因為我們並不關心他們是誰,發出命令的人發出命令,可以繼續做其他的事情,接受命令的人執行任務就可以,不需要你發出命令,還要監督我們完成,只要我們完成任務是合格的就行。這種行為也就是“解耦”,命令模式用得比較少,一般都是在實際項目開發的後發現需要用的時候我們通過重構來實現。在現實生活中,我們也用這個設計模式,例如:我們去吃大排檔,到店告訴服務員來盤啤酒小龍蝦、烤魚一份,一箱啤酒,過了一會告訴服務員不要烤魚了,這裏我們不需要關心具體是哪個大廚炒菜,只需要告訴服務員我們需要什麽就可以了
命令模式介紹
命令模式的定義
將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數化,對請求排隊或記錄請求日誌,以及支持可撤銷的操作
命令模式的結構圖
命令模式的組成
從命令模式的結構圖可以看出,它涉及到五個角色,它們分別是:
(1)、客戶角色(Client):創建具體的命令對象,並且設置命令對象的接收者。註意這個不是我們常規意義上的客戶端,而是在組裝命令對象和接收者,或許,把這個Client稱為裝配者會更好理解,因為真正使用命令的客戶端是從Invoker來觸發執行。
(2)、命令角色(Command):聲明了一個給所有具體命令類實現的抽象接口。
(3)、具體命令角色(ConcreteCommand)
(4)、請求者角色(Invoker):要求命令對象執行請求,通常會持有命令對象,可以持有很多的命令對象。這個是客戶端真正觸發命令並要求命令執行相應操作的地方,也就是說相當於使用命令對象的入口。
(5)、接受者角色(Receiver):接收者,真正執行命令的對象。任何類都可能成為一個接收者,只要它能夠實現命令要求實現的相應功能。
命令模式的代碼實現
第一版無模式
class Program { static void Main(string[] args) { Barbecuer boy = newView CodeBarbecuer(); boy.BakeMutton(); boy.BakeMutton(); boy.BakeMutton(); boy.BakeChickenWing(); boy.BakeMutton(); boy.BakeMutton(); boy.BakeChickenWing(); Console.Read(); } } //烤肉串者 public class Barbecuer { //烤羊肉 public void BakeMutton() { Console.WriteLine("烤羊肉串!"); } //烤雞翅 public void BakeChickenWing() { Console.WriteLine("烤雞翅!"); } }這種方法可以看出命令的發送者將對命令接收者是強耦合的關系
第二版引入命令模式
//客戶端 class Program { static void Main(string[] args) { //開店前的準備 Barbecuer boy = new Barbecuer(); Command bakeMuttonCommand1 = new BakeMuttonCommand(boy); Command bakeMuttonCommand2 = new BakeMuttonCommand(boy); Command bakeChickenWingCommand1 = new BakeChickenWingCommand(boy); Waiter girl = new Waiter(); //開門營業 顧客點菜 girl.SetOrder(bakeMuttonCommand1); girl.SetOrder(bakeMuttonCommand2); girl.SetOrder(bakeChickenWingCommand1); //點菜完閉,通知廚房 girl.Notify(); Console.Read(); } //服務員 public class Waiter { private IList<Command> orders = new List<Command>(); //設置訂單 public void SetOrder(Command command) { if (command.ToString() == "命令模式.BakeChickenWingCommand") { Console.WriteLine("服務員:雞翅沒有了,請點別的燒烤。"); } else { orders.Add(command); Console.WriteLine("增加訂單:" + command.ToString() + " 時間:" + DateTime.Now.ToString()); } } //取消訂單 public void CancelOrder(Command command) { orders.Remove(command); Console.WriteLine("取消訂單:" + command.ToString() + " 時間:" + DateTime.Now.ToString()); } //通知全部執行 public void Notify() { foreach (Command cmd in orders) { cmd.ExcuteCommand(); } } } //抽象命令 public abstract class Command { protected Barbecuer receiver; public Command(Barbecuer receiver) { this.receiver = receiver; } //執行命令 abstract public void ExcuteCommand(); } //烤羊肉串命令 class BakeMuttonCommand : Command { public BakeMuttonCommand(Barbecuer receiver) : base(receiver) { } public override void ExcuteCommand() { receiver.BakeMutton(); } } //烤雞翅命令 class BakeChickenWingCommand : Command { public BakeChickenWingCommand(Barbecuer receiver) : base(receiver) { } public override void ExcuteCommand() { receiver.BakeChickenWing(); } } //烤肉串者 public class Barbecuer { public void BakeMutton() { Console.WriteLine("烤羊肉串!"); } public void BakeChickenWing() { Console.WriteLine("烤雞翅!"); } }View Code
命令模式的實現場景
在下面的情況下可以考慮使用命令模式:
- 系統需要支持命令的撤銷(undo)。命令對象可以把狀態存儲起來,等到客戶端需要撤銷命令所產生的效果時,可以調用undo方法吧命令所產生的效果撤銷掉。命令對象還可以提供redo方法,以供客戶端在需要時,再重新實現命令效果。
- 系統需要在不同的時間指定請求、將請求排隊。一個命令對象和原先的請求發出者可以有不同的生命周期。意思為:原來請求的發出者可能已經不存在了,而命令對象本身可能仍是活動的。這時命令的接受者可以在本地,也可以在網絡的另一個地址。命令對象可以串行地傳送到接受者上去。
- 如果一個系統要將系統中所有的數據消息更新到日誌裏,以便在系統崩潰時,可以根據日誌裏讀回所有數據的更新命令,重新調用方法來一條一條地執行這些命令,從而恢復系統在崩潰前所做的數據更新。
- 系統需要使用命令模式作為“CallBack(回調)”在面向對象系統中的替代。Callback即是先將一個方法註冊上,然後再以後調用該方法。
命令模式優缺點
命令模式使得命令發出的一個和接收的一方實現低耦合,從而有以下的優點:
- 命令模式使得新的命令很容易被加入到系統裏。
- 可以設計一個命令隊列來實現對請求的Undo和Redo操作。
- 可以較容易地將命令寫入日誌。
- 可以把命令對象聚合在一起,合成為合成命令。合成命令式合成模式的應用。
命令模式的缺點:
- 使用命令模式可能會導致系統有過多的具體命令類。這會使得命令模式在這樣的系統裏變得不實際。
總結
命令模式能叫容易地設計一個命令隊列,第二,在需要的情況下,可以較容易地將命令記入日誌,第三,允許接受請求的一方是否否決請求。第四,可以容易地實現對請求的撤銷與重做,第五,由於加進新的具體命令類不影響其它的類,因此新增新的具體命令類很容易,第六,命令模式把請求一個操作的對象與知道怎麽執行一個操作的對象分割開
永遠不要讓自己原地踏步--
c#設計模式系列:命令模式(Command Pattern)