《Head.First设计模式》的学习笔记(7)--命令模式
背景:有时候我们需要对方法进行封装,通过对这些封装的方法进行调用,我们可以很好的处理一些事情。比如,记录日志,或者重复使用这些封装实现撤销功能。
意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
结构:
例子:这次我们将设计一个家电自动化遥控器的API
需求分析:
1)、遥控器上具有七个可编程的插槽、七个开关按钮和一个整体撤销按钮。
2)、通过创建一组API,使插槽可以控制一个或一组家电装置,这些装置指电灯、电风扇、热水器等。
3)、插槽还可以控制未来可能出现的家电装置。
4)、整体撤销按钮具有撤销上一个命令的功能。
初步思考:
1)、我们将设计一系列类,这些类都具有ON()和Off()方法。
2)、当遥控器上的on或off开关被按下时,某些类的on或off方法被调用,进而控制家电装置,但这些被调用的类是可以被改变的(因为插槽上的东西可以改变)。
3)、当on或off开关被按下时,如果我们通过if--else语句加以选择判断,那么第三点需求我们将难以满足。
4)撤销功能如何实现,上一个按钮是什么?针对的是哪一个装置?
进一步思考:
这次设计中不变的是遥控器(或者说是遥控器的按钮),变化的是家电装置,例如第一排开关可以控制电灯,也可以控制电风扇,或者未来可能出现的家电。所以我们需要对遥控器和家电装置进行解耦。此时我们想到了命令模式:遥控器(或者说遥控器上的按钮)就是命令的请求者,家电装置就是命令的执行者,我们所要做的就是将命令的请求者和命令的执行者解耦。
具体的过程:
1)、客户创建命令。
2)、客户将命令的执行者封装进命令对象里。
3)、命令的请求者调用命令。
4)、命令的执行者执行命令。
下面我们实现只有一组开关按钮的遥控器,类图如下:
代码如下:
2 {
3 static void Main(string[] args)
4 {
5 SimpleRemoteControl remoteControl = new SimpleRemoteControl();
6 Light light = new Light();
7 LightOnCommand lightOnCommand = new LightOnCommand(light);
8 remoteControl.SetCommand(lightOnCommand);
9 remoteControl.ButtonWasPressed();
10 Console.ReadLine();
11 }
12 }
13 public class Light
14 {
15 public void On()
16 {
17 Console.WriteLine("Light is on");
18 }
19
20 public void Off()
21 {
22 Console.WriteLine("Light is off");
23 }
24 }
25public class LightOnCommand : Command
26 {
27 Light light;
28
29 public LightOnCommand(Light paramLight)
30 {
31 this.light = paramLight;
32 }
33
34 public void Execute()
35 {
36 light.On();
37 }
38 }
39public interface Command
40 {
41 void Execute();
42 }
43public class SimpleRemoteControl
44 {
45 Command slot;
46
47 public void SetCommand(Command paramCommand)
48 {
49 this.slot = paramCommand;
50 }
51
52 public void ButtonWasPressed()
53 {
54 slot.Execute();
55 }
56 }
注释:命令的请求者:SimpleRemoteControl
命令的执行者:Light
命令接口:Command
具体的命令:LightOnCommand
客户(封装命令的):RemoteLodde
我们的遥控器共有七个插槽,可以用数组来实现,这里不写了。撤销按钮的功能可以通过栈来实现(对Command进行入栈,出栈即可)。
补充:
1)NoCommand模式
代码:
2 {
3 public void Execute()
4 {
5 }
6 }
2)、宏命令
代码:
2 {
3 List<Command> commands;
4
5 public MacroCommand(List<Command> commands)
6 {
7 this.commands = commands;
8 }
9
10 public void Execute()
11 {
12 foreach (Command command in commands)
13 {
14 command.Execute();
15 }
16 }
17 }
应用:
1)、队列请求
想象有一个工作队列;你在某一端添加命令,然后另一端则是线程。线程进行下面的动作:从队列中取出一个命令,调用他的execute()方法,等待这个命令完成,然后将此命令丢弃,再取出下一个命令...此时,工作队列类和进行计算的对象之间完全解耦。当时线程可能进行财务运算,下一刻可能读取网络数据。
2)、日志请求
这需要我们将所有的动作(命令)记录在日志中,并能在系统死机后,对这些命令对象重新加载,成批的依次调用这些对象的execute()方法,恢复到之前的状态。比方说,对于电子表格的应用,我们可能想要实现的错误恢复方式是将电子表格的操作记录在日志中,而不是每次电子表格一有变化就记录整个电子表格。