封裝調用-命令模式
?
命令模式可將“動作的請求者”從“動作的執行者”對象中解耦。
本篇中將不再描述書中所引入的“巴斯特家電自動化公司”的遙控器控制案例,而使用簡單易懂的餐廳案例。
在開始之前,讓我們通過一個現實中的例子來了解命令模式。
理解命令模式
讓我們回到餐廳,研究顧客、女招待、訂單,以及快餐廚師之間的交互。通過這樣的互動,你將體會到命令模式所涉及的對象,也會知道它們之間如何被解耦。

將以上流程代入到編程的對象中進一步思考對象與方法之間的關系:

分析餐廳對應的角色與職責
1、顧客:發出請求的對象。
2、訂單:封裝了準備餐點的請求。
3、女招待:接收訂單,然后調用訂單的orderUp方法,將訂單提交到柜臺,無需知道訂單細節。
4、廚師:收到訂單后,按訂單實現對應餐點的所有方法制作餐點。
從餐廳到命令模式

命令模式類圖

- Command:定義命令的接口,聲明執行的方法。
- ConcreteCommand: 具體的命令, 實現命令接口;通常會持有接收者,并調用接收者的功能來完成命令要執行的操作。
- Receiver:接收者,真正執行命令的對象。任何類都可能成為一個接收者,只要它能夠實現命令要求實現的相應功能。
- Invoker:要求命令對象執行請求,通常會持有命令對象,可以持有很多的命令對象。這個是客戶端真正觸發命令并要求命令執行相應操作的地方,也就是說相當于使用命令對象的入口。
- Client: 創建具體的命令對象,并且設置命令對象的接收者。注意這個不是我們常規意義上的客戶端,而是在組裝命令對象和接收者,或許,把這個Client稱為裝配者會更好理解,因為真正使用命令的客戶端是從Invoker來觸發執行。
命令模式定義:將“請求”封裝成對象,以便使用不同的請求、隊列或者日志來參數化其它對象。命令模式也支持可撤銷的操作。
實現命令接口:
1 2 3 | public abstract class Command{ ???? public abstract void Execute(); } |
OrderCommand:具體的命令,繼承自Command抽象類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class OrderCommand? implements Command{ ???? //持有接受者對象 ???? SeniorChef receiver; ???? Order order; ???? public OrderCommand(SeniorChef receiver, Order order){ ???????? this .receiver = receiver; ????????? this .order = order; ???? } ???? public override? void Execute(){ ???????? Console.WriteLine( "{0}桌的訂單:" , order.DiningTable); ???????? foreach (string item in order.FoodDic.Keys){ ???????????? //通常會轉調接收者對象的相應方法,讓接收者來真正執行功能 ???????????? receiver.MakeFood(order.FoodDic[item],item); ???????? } ???????? Thread.Sleep( 2000 ); //停頓一下 模擬做飯的過程 ???????? Console.WriteLine( "{0}桌的飯弄好了" , order.DiningTable); ???? } } |
Invoker調用者,seniorChef:接收者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class Waiter{ ???? ArrayList commands =? null ; //可以持有很多的命令對象 ???? public Waiter(){ ???????? commands =? new ArrayList(); ???? } ???? public void SetCommand(Command cmd){ ???????? commands.Add(cmd); ???? } ???? //提交訂單 喊 訂單來了,廚師開始執行 ???? public void OrderUp(){ ???????? Console.WriteLine( "美女服務員:叮咚,大廚,新訂單來了......." ); ???????? Console.WriteLine( "資深廚師:收到" ); ???????? for ( int i =? 0 ; i < commands.Count; i++){ ???????????? Command cmd = commands[i] as Command; ???????????? if (cmd !=? null ){ ???????????????? cmd.Execute(); ???????????? } ???????? } ???? } } |
1 2 3 4 5 | public class SeniorChef{ ???? public void MakeFood( int num,string foodName){ ???????? Console.WriteLine( "{0}份{1}" , num,foodName); ???? } } |
訂單Order,封裝訂單內容,然后傳入OrderCommand,將訂單對象變為命令對象
1 2 3 4 5 6 | public class Order{ ???? // 餐桌號碼 ???? public int DiningTable { set; get; } ???? // food key:飯名 value:多少份 ???? public Dictionary FoodDic { set; get; } } |
測試端Program相當于Client角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | class Program{ ???? public static void main(string[] args){ ???? //program類 作為客戶端 ???? //創建2個order ???? Order order1 =? new Order(); ???? order1.DiningTable =? 1 ; ???? order1.FoodDic =? new Dictionary() ; ???? order1.FoodDic.Add( "西紅柿雞蛋面" , 1 ); ???? order1.FoodDic.Add( "小杯可樂" , 2 ); ???? Order order2 =? new Order(); ???? order2.DiningTable =? 3 ; ???? order2.FoodDic =? new Dictionary(); ???? order2.FoodDic.Add( "尖椒肉絲蓋飯" ,? 1 ); ???? order2.FoodDic.Add( "小杯雪碧" ,? 1 ); ???? //創建接收者 ???? SeniorChef receiver= new SeniorChef(); ???? //將訂單這個兩個消息封裝成命令對象 ???? OrderCommand cmd1 =? new OrderCommand(receiver, order1); ???? OrderCommand cmd2 =? new OrderCommand(receiver, order2); ???? //創建調用者 waitor ???? Waitor invoker =? new Waitor(); ???? //添加命令 ???? invoker.SetCommand(cmd1); ???? invoker.SetCommand(cmd2); ???? //將訂單帶到柜臺 并向廚師喊 訂單來了 ???? invoker.OrderUp(); ???? Console.Read(); ???? } } |
總結
命令模式優點:
1.降低對象之間的耦合度。
2.新的命令可以很容易地加入到系統中。
3.可以比較容易地設計一個組合命令。
4.調用同一方法實現不同的功能
缺點:
使用命令模式可能會導致某些系統有過多的具體命令類。因為針對每一個命令都需要設計一個具體命令類,因此某些系統可能需要大量具體命令類,這將影響命令模式的使用。
適用環境:
1.系統需要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互。
2.系統需要在不同的時間指定請求、將請求排隊和執行請求。
3.系統需要支持命令的撤銷(Undo)操作和恢復(Redo)操作。
4.系統需要將一組操作組合在一起,即支持宏命令。