命令模式(Command):
将请求封装成对象,以便使用不同的请求、日志、队列等来参数化其他对象。命令模式也支持撤销操作。
命令模式的角色:
1)传递命令对象(Invoker):是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
2)抽象命令接口(Command):声明执行命令的接口,拥有执行命令的抽象方法execute()。
3)具体的命令对象(ConcreteCommand):是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
4)接受者对象(Receiver):执行命令功能的相关操作,是具体命令对象业务的真正实现者。
5)客户端对象(Client):创建具体命令的对象并且设置命令对象的接受者。
示例:
土豪小明是一个狂热的科技迷,因此将家中电器进行了全面升级,更换成了智能家电,通过小爱同学可以统一控制,只需要一句"小爱同学,打开电灯"...,那么我们的故事就从这里开始。
1 internal class Program 2 { 3 private static void Main(string[] args) 4 { 5 // 创建电灯的对象 6 LightReceiver lightReceiver = new LightReceiver(); 7 // 创建电灯的开关命令 8 Command lightOnCommand = new LightOnCommand(lightReceiver); 9 Command lightOffCommand = new LightOffCommand(lightReceiver); 10 // 创建遥控器-小爱同学 11 RemoteController remoteController = new RemoteController(); 12 // 设置 13 remoteController.setCommand(0, lightOnCommand, lightOffCommand); 14 // 执行命令 15 remoteController.onExecute(0); 16 remoteController.Undo(); 17 } 18 } 19 20 /// <summary> 21 /// 抽象命令接口 22 /// </summary> 23 public interface Command 24 { 25 /// <summary> 26 /// 执行 27 /// </summary> 28 void execute(); 29 30 /// <summary> 31 /// 撤销 32 /// </summary> 33 void undo(); 34 } 35 36 /// <summary> 37 /// 接受者对象 38 /// </summary> 39 public class LightReceiver 40 { 41 public void on() 42 { 43 Console.WriteLine("电灯打开了..."); 44 } 45 46 public void off() 47 { 48 Console.WriteLine("电灯关闭了..."); 49 } 50 } 51 52 /// <summary> 53 /// 具体的命令对象 54 /// </summary> 55 internal class LightOnCommand : Command 56 { 57 private readonly LightReceiver lightReceiver; 58 59 public LightOnCommand(LightReceiver lightReceiver) 60 { 61 this.lightReceiver = lightReceiver; 62 } 63 64 public void execute() 65 { 66 // 调用接收者的方法 67 lightReceiver.on(); 68 } 69 70 public void undo() 71 { 72 // 调用接收者的方法 73 lightReceiver.off(); 74 } 75 } 76 77 /// <summary> 78 /// 具体的命令对象 79 /// </summary> 80 internal class LightOffCommand : Command 81 { 82 private readonly LightReceiver lightReceiver; 83 84 public LightOffCommand(LightReceiver lightReceiver) 85 { 86 this.lightReceiver = lightReceiver; 87 } 88 89 public void execute() 90 { 91 // 调用接收者的方法 92 lightReceiver.off(); 93 } 94 95 public void undo() 96 { 97 // 调用接收者的方法 98 lightReceiver.on(); 99 } 100 } 101 102 /// <summary> 103 /// 空命令,用于初始化 104 /// </summary> 105 internal class NoCommand : Command 106 { 107 public void execute() 108 { 109 } 110 111 public void undo() 112 { 113 } 114 } 115 116 internal class RemoteController 117 { 118 // 按钮的命令数组 119 private Command[] onCommands; 120 121 private Command[] offCommands; 122 123 // 撤销命令 124 private Command undoCommand; 125 126 public RemoteController() 127 { 128 // 假设小爱同学需要连接5款智能家电 129 onCommands = new Command[5]; 130 offCommands = new Command[5]; 131 for (int i = 0; i < 5; i++) 132 { 133 onCommands[i] = new NoCommand(); 134 offCommands[i] = new NoCommand(); 135 } 136 } 137 138 // 给小爱同学设置需要的命令 139 public void setCommand(int no, Command onCommand, Command offCommand) 140 { 141 onCommands[no] = onCommand; 142 offCommands[no] = offCommand; 143 } 144 145 // 对小爱同学下达打开命令 146 public void onExecute(int no) 147 { 148 // 小爱同学执行打开操作 149 onCommands[no].execute(); 150 // 记录操作,用于撤回 151 undoCommand = onCommands[no]; 152 } 153 154 // 对小爱同学下达关闭命令 155 public void offExecute(int no) 156 { 157 // 小爱同学执行关闭操作 158 offCommands[no].execute(); 159 // 记录操作,用于撤回 160 undoCommand = offCommands[no]; 161 } 162 163 /// <summary> 164 /// 撤销上次操作 165 /// </summary> 166 public void Undo() 167 { 168 undoCommand.undo(); 169 } 170 }
命令模式的优缺点:
优点:
1)降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
2)增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足OCP原则,对扩展比较灵活。
3)可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
4)方便实现 Undo 和 Redo 操作。命令模式可以与备忘录模式结合,实现命令的撤销与恢复。
缺点:可能产生大量的具体命令类。
适用环境:
1)使用命令模式作为“CallBack”在面向对象系统中的替代。“CallBack”讲的便是先将一个函数登记上,然后在以后调用此函数。
2)需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。
3)系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。
4)如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
命令模式与策略模式的区别:
1)命令模式与策略模式都封装了变化,但命令模式封装的是请求的变化,而策略模式封装的是算法的变化。
2)命令模式可以抽象化成策略模式。策略模式较简单,而命令模式比较复杂。策略模式聚焦的是对相同请求更换解决方案的灵活性;而命令模式聚焦的是对多请求变化的封装以及对相同请求不同的请求形式解决方法的可复用性。