封装调用-命令模式
命令模式可将“动作的请求者”从“动作的执行者”对象中解耦。
本篇中将不再描述书中所引入的“巴斯特家电自动化公司”的遥控器控制案例,而使用简单易懂的餐厅案例。
在开始之前,让我们通过一个现实中的例子来了解命令模式。
理解命令模式
让我们回到餐厅,研究顾客、女招待、订单,以及快餐厨师之间的交互。通过这样的互动,你将体会到命令模式所涉及的对象,也会知道它们之间如何被解耦。
将以上流程代入到编程的对象中进一步思考对象与方法之间的关系:
分析餐厅对应的角色与职责
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.系统需要将一组操作组合在一起,即支持宏命令。