命令模式
一、 基本概述
问题:假设有一个控制器,该控制器上有7个可编程的插槽,每个都可以指定到一个不同的家电装置,每个插槽都有对应的开关按钮。这个遥控器还具备一个整体的撤销按钮。厂家已经提供了控制家电基本处理类。希望你能够创建一组控制遥控器的API,让每个插槽都能够控制一个或一组装置。(如下图,厂商类)
分析1:厂家提供的类,有许多都具备On()和off()方法,除此之外还有一些其他的五花八门的方法。
分析2:还不只这样,听起来似乎将来还会有更多的厂商类,而且每个类还会有各式各样的方法。
分析3:遥控器应该知道如何解读按钮被按下的动作,然后发出正确的请求,但是遥控器不需知道这些家电自动化的细节。
分析4:我们不想让遥控器包含一大堆if语句,例如“if slot1==Light,then light.On(),else if slot1==Hottub,then hottub.JetsOn()”.大家都知道这样的设计很糟糕。且只要有新的厂商类进来,就必须修改现有的代码,造成潜在的错误。
分析5:命令模式可以将“动作的请求者”从“动作的执行者”对象中解耦,在这个例子中,请求者可以是遥控器,而执行者对象就是厂商类其中之一的实例。
分析6:利用命令对象,把请求(例如打开电灯)封装成一个特定对象(例如客厅电灯对象)。所以,如果对每个按钮都存储一个命令对象,那么当按钮被按下的时候,就可以请命令对象做相关的工作。遥控器并不需要知道工作内容是什么,只要有个命令对象能喝正确的对象沟通,把事情做好就可以了。
分析7:使用这个模式,我们能够创建一个API,将这些命令对象加载到按钮插槽,让遥控器的代码尽量保持简单。而把家电自动化的工作和进行该工作的对象一起封装在命令对象中。
二、详细说明
命令模式:将请求封装成对象,这可以让你使用不同的请求、队列,或者日志请求来参数化其他对象。命令模式也可以支持撤销操作。
现在仔细看这个定义,我们知道一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包进对象中。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行了哪些动作,只知道如果调用execute()方法,请求的目的就能达到。
1.定义命令模式类图:
2.下面的类图提供了设计的全貌:
问:如果拥有了一个遥控器,却无法光凭按下一个按钮,就同时能弄暗灯光、打开音响和电视、设置好DVD,并让热水器开始加温,那么要这个遥控器还有什么意义?
答:此时就可以用到命令模式的延伸“宏命令”,制造一种新的命令,用来执行其他一堆命令。在宏命令中,用命令数组(集合)存储一大堆命令,当这个宏命令被遥控器执行时,就一次性执行数组里的每个命令。(具体内容,可在代码列表查看)
问:接收者一定有必要存在吗?为何命令对象不直接实现execute()方法的细节。
答:一般来说,我们尽量设计“傻瓜”命令对象,它只懂得调用一个接收者的一个行为。然而,有许多“聪明”命令对象会实现许多逻辑,直接完成一个请求。当然你可以设计聪明的命令对象,只是这样一来,调用者和接收者之间的解耦程度是比不上“傻瓜”命令对象的,而且,你也不能够把接收者当做参数传给命令。
问:我可以创建PartyCommand,然后在它的execute()方法中调用其他的命令,利用这种做法实现Party模式(Party Mode)吗?
答:你可以这么做。然后,这等于把Party模式“硬编码”到PartyCommand中。为什么要这么麻烦呢?利用宏命令,你可以动态地决定PartyCommand是由哪些命令组成,所以宏命令在使用上更灵活。一般来说,宏命令的做法更优雅,也需要较少的新代码。
问:我如何能够实现多层次的撤销操作?换句话说,我希望能够按下撤销按钮许多次,撤销到很早很早以前的状态。
答:其实这相当容易做到,不要只记录最后一个被执行的命令,而使用一个堆栈记录操作过程的每一个命令。然后,不管什么时候按下了撤销按钮,你都可以从堆栈中取出最上层的命令,然后调用它的Undo()方法。
3.命令模式的更多用途:
(1)队列请求:命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。它甚至可以在不同的线程中被调用。我们可以利用这样的特性衍生一些应用,例如:日程安排、线程池、工作队列等。
想象有一个工作队列,你在某一端添加命令,然后另一端则是线程,线程进行下面的动作,从队列中取出一个命令,调用它的Execute()方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令......
(2)日志请求:某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态。通过新增两个方法(Store()、Load()),命令模式能够支持这一点。这种日志的方式对于遥控器来说没有意义,然而,有许多调用大型数据结构的动作的应用,无法在每次改变发生时被快速地存储。通过使用记录日志,我们可以将上次检查点之后的所有操作记录下来,如果系统出现状况,从检查点开始应用这些操作。
比方说,对于电子表格应用,我们可能想要实现的错误恢复方式是将电子表格的操作记录在日志中,而不是每次电子表格一有变化就记录整个电子表格。对更高级的应用而言,这些技巧可以被扩展应用到事务处理中,也就是说,一整组操作必须完成后才算有效。
4.总结:
(1)当需要将发出请求的对象和执行请求的对象解耦的时候,使用命令模式
(2)在被解耦的两者之间是通过命令对象进行沟通的,命令对象封装了接收者和一个或一组动作。
(3)调用者通过调用命令对象的execute()发出请求,这会使得接收者的动作被调用。
(4)调用者可以接受命令当做参数,甚至在运行时动态地进行。
(5)命令可以支持撤销,做法是实现一个Undo()方法来回到execute()被执行前的状态。
(6)宏命令是命令的一种简单的延伸,允许调用多个命令,宏方法也可以支持撤销。
(7)命令也可以用来实现日志和事务系统。
三、代码列表
/// <summary> /// 命令接口 /// </summary> public interface ICommand { /// <summary> /// 执行 /// </summary> void Execute(); /// <summary> /// 撤销 /// </summary> void Undo(); } public class RemoteControl { private ICommand[] onCommands; private ICommand[] offCommands; private ICommand undoCommand; public RemoteControl() { onCommands = new ICommand[7]; offCommands = new ICommand[7]; ICommand noCommand = new NoCommand(); for (int i = 0; i < 7; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } undoCommand = noCommand; } public void SetCommand(int slot, ICommand onCommand, ICommand offCommand) { onCommands[slot] = onCommand; offCommands[slot] = offCommand; } public void OnButtonWasPushed(int slot) { onCommands[slot].Execute(); undoCommand = onCommands[slot]; } public void OffButtonWasPushed(int slot) { offCommands[slot].Execute(); undoCommand = offCommands[slot]; } public void UndoButtonWasPushed() { undoCommand.Undo(); } public override string ToString() { StringBuilder sb=new StringBuilder(); sb.Append("\n---------Remote Control----------\n"); for (int i = 0; i < onCommands.Length; i++) { sb.AppendFormat("[slot {0}] {1} {2}",i,onCommands[i].GetType().FullName, offCommands[i].GetType().FullName); sb.AppendLine(); } return sb.ToString(); } } public class SimpleRemoteControl { private ICommand slot; public void SetCommand(ICommand command) { slot = command; } public void ButtonWasPressed() { slot.Execute(); } } //Worker /// <summary> /// 吊扇 /// </summary> public class CeilLingFan { public CeilLingFanSpeed speed { get; private set; } = 0; private string name; public CeilLingFan(string name) { this.name = name; } /// <summary> /// 高速 /// </summary> public void High() { speed = CeilLingFanSpeed.High; Console.WriteLine("{0} ceilLingFan is highs", name); } /// <summary> /// 中速 /// </summary> public void Medium() { speed = CeilLingFanSpeed.Medium; Console.WriteLine("{0} ceilLingFan is medium", name); } /// <summary> /// 低速 /// </summary> public void Low() { speed = CeilLingFanSpeed.Low; Console.WriteLine("{0} ceilLingFan is low", name); } /// <summary> /// 关闭 /// </summary> public void Off() { speed = CeilLingFanSpeed.Off; Console.WriteLine("{0} ceilLingFan is Off", name); } public enum CeilLingFanSpeed { High = 3, Medium = 2, Low = 1, Off = 0 } } /// <summary> /// 车库门 /// </summary> public class GarageDoor { private string name; public GarageDoor(string name) { this.name = name; } /// <summary> /// 上升 /// </summary> public void Up() { Console.WriteLine("{0} Garage door is open", name); } /// <summary> /// 下降 /// </summary> public void Down() { Console.WriteLine("{0} Garage door is close", name); } /// <summary> /// 停止 /// </summary> public void Stop() { Console.WriteLine("{0} Garage door is stop", name); } /// <summary> /// 灯光开启 /// </summary> public void LightOn() { Console.WriteLine("{0} Garage Light is on", name); } /// <summary> /// 灯光关闭 /// </summary> public void LightOff() { Console.WriteLine("{0} Garage Light is off", name); } } /// <summary> /// 热浴盆 /// </summary> public class Hottub { /// <summary> /// 水循环 /// </summary> public void Circulata() { Console.WriteLine("Tub water is circulata"); } /// <summary> /// 喷射打开 /// </summary> public void JetsOn() { Console.WriteLine("Jets is on"); } /// <summary> /// 喷射关闭 /// </summary> public void JetsOff() { Console.WriteLine("Jets is Off"); } /// <summary> /// 设置温度 /// </summary> public void SetTemperature() { Console.WriteLine("Default temperature is 46°C"); } } /// <summary> /// 灯光 /// </summary> public class Light { private string name; public Light(string name) { this.name = name; } /// <summary> /// 开启 /// </summary> public void On() { Console.WriteLine("{0} light is On", name); } /// <summary> /// 关闭 /// </summary> public void Off() { Console.WriteLine("{0} light is Off", name); } } /// <summary> /// 立体声 /// </summary> public class Stereo { private string name; public Stereo(string name) { this.name = name; } /// <summary> /// 开启 /// </summary> public void On() { Console.WriteLine("{0} Stereo is on", name); } /// <summary> /// 关闭 /// </summary> public void Off() { Console.WriteLine("{0} Stereo is off", name); } /// <summary> /// 设置CD /// </summary> public void SetCd() { Console.WriteLine("Stereo Cd is on"); } /// <summary> /// 设置DVD /// </summary> public void SetDvd() { Console.WriteLine("Stereo Dvd is on"); } /// <summary> /// 设置收音机 /// </summary> public void SetRadio() { Console.WriteLine("Stereo radio is on"); } /// <summary> /// 设置音量 /// </summary> public void SetVolume(byte volume) { Console.WriteLine("Stereo volume is {0}", volume); } } /// <summary> /// 电视 /// </summary> public class TV { private string name; public TV(string name) { this.name = name; } /// <summary> /// 开启 /// </summary> public void On() { Console.WriteLine("{0} TV is on", name); } /// <summary> /// 关闭 /// </summary> public void Off() { Console.WriteLine("{0} TV is off", name); } /// <summary> /// 设置频道 /// </summary> public void SetInputChannel() { Console.WriteLine("Defualt channel is CCTV1"); } /// <summary> /// 设置音量 /// </summary> public void SetVolume() { Console.WriteLine("Defualt volume is 5"); } } //Commands /// <summary> /// 吊扇基础命令类 /// </summary> public abstract class CeilingFanCommand : ICommand { protected CeilLingFan ceilLingFan; private CeilLingFan.CeilLingFanSpeed speed; public void Execute() { speed = ceilLingFan.speed; FanExecute(); } public abstract void FanExecute(); public void Undo() { switch (speed) { case CeilLingFan.CeilLingFanSpeed.High: ceilLingFan.High(); break; case CeilLingFan.CeilLingFanSpeed.Medium: ceilLingFan.Medium(); break; case CeilLingFan.CeilLingFanSpeed.Low: ceilLingFan.Low(); break; case CeilLingFan.CeilLingFanSpeed.Off: ceilLingFan.Off(); break; } } } public class CeilLingFanHighCommand : CeilingFanCommand { public CeilLingFanHighCommand(CeilLingFan ceilLingFan) { base.ceilLingFan = ceilLingFan; } public override void FanExecute() { ceilLingFan.High(); } } public class CeilLingFanLowCommand : CeilingFanCommand { public CeilLingFanLowCommand(CeilLingFan ceilLingFan) { base.ceilLingFan = ceilLingFan; } public override void FanExecute() { ceilLingFan.Low(); } } public class CeilLingFanMediumCommand : CeilingFanCommand { public CeilLingFanMediumCommand(CeilLingFan ceilLingFan) { base.ceilLingFan = ceilLingFan; } public override void FanExecute() { ceilLingFan.Medium(); } } public class CeilLingFanOffCommand : CeilingFanCommand { public CeilLingFanOffCommand(CeilLingFan ceilLingFan) { base.ceilLingFan = ceilLingFan; } public override void FanExecute() { ceilLingFan.Off(); } } public class GarageDoorCloseCommand : ICommand { private GarageDoor garageDoor; public GarageDoorCloseCommand(GarageDoor garageDoor) { this.garageDoor = garageDoor; } public void Execute() { garageDoor.Down(); garageDoor.LightOff(); } public void Undo() { garageDoor.LightOn(); garageDoor.Up(); } } public class GarageDoorOpenCommand : ICommand { private GarageDoor garageDoor; public GarageDoorOpenCommand(GarageDoor garageDoor) { this.garageDoor = garageDoor; } public void Execute() { garageDoor.Up(); garageDoor.LightOn(); } public void Undo() { garageDoor.Down(); garageDoor.LightOff(); } } public class HottubOffCommand:ICommand { private Hottub hottub; public HottubOffCommand(Hottub hottub) { this.hottub = hottub; } public void Execute() { hottub.JetsOff(); } public void Undo() { hottub.JetsOn(); } } public class HottubOnCommand : ICommand { private Hottub hottub; public HottubOnCommand(Hottub hottub) { this.hottub = hottub; } public void Execute() { hottub.JetsOn(); hottub.SetTemperature(); } public void Undo() { hottub.JetsOff(); } } public class LightOffCommand:ICommand { private Light light; public LightOffCommand(Light light) { this.light = light; } public void Execute() { light.Off(); } public void Undo() { light.On(); } } public class LightOnCommand : ICommand { private Light light; public LightOnCommand(Light light) { this.light = light; } public void Execute() { light.On(); } public void Undo() { light.Off(); } } /// <summary> /// 更多命令(宏命令) /// </summary> public class MacroCommand:ICommand { private ICommand[] commands; public MacroCommand(ICommand[] commands) { this.commands = commands; } public void Execute() { foreach (ICommand command in commands) { command.Execute(); } } public void Undo() { foreach (ICommand command in commands) { command.Undo(); } } } /// <summary> /// 没有命令 /// </summary> public class NoCommand : ICommand { public void Execute() { } public void Undo() { } } public class StereoOffCommand : ICommand { private Stereo stereo; public StereoOffCommand(Stereo stereo) { this.stereo = stereo; } public void Execute() { stereo.Off(); } public void Undo() { stereo.On(); } } public class StereoOnCommand : ICommand { private Stereo stereo; public StereoOnCommand(Stereo stereo) { this.stereo = stereo; } public void Execute() { stereo.On(); } public void Undo() { stereo.Off(); } } public class StereoOnWithCDCommand:ICommand { private Stereo stereo; public StereoOnWithCDCommand(Stereo stereo) { this.stereo = stereo; } public void Execute() { stereo.On(); stereo.SetCd(); stereo.SetVolume(11); } public void Undo() { stereo.Off(); } } public class TVOffCommand:ICommand { private TV tv; public TVOffCommand(TV tv) { this.tv = tv; } public void Execute() { tv.Off(); } public void Undo() { tv.On(); } } public class TVOnCommand : ICommand { private TV tv; public TVOnCommand(TV tv) { this.tv = tv; } public void Execute() { tv.On(); tv.SetInputChannel(); tv.SetVolume(); } public void Undo() { tv.Off(); } } //RunTest [Test] public void RemoteLoader() { //单个插槽的简单控制装置 /*SimpleRemoteControl remote = new SimpleRemoteControl(); Light light = new Light(); GarageDoor garageDoor = new GarageDoor(); LightOnCommand lightOn = new LightOnCommand(light); GarageDoorOpenCommand garageOpen = new GarageDoorOpenCommand(garageDoor); remote.SetCommand(lightOn); remote.ButtonWasPressed(); remote.SetCommand(garageOpen); remote.ButtonWasPressed();*/ //多个插槽(含有关闭功能)的控制装置 /*RemoteControl remoteControl = new RemoteControl(); Light livingRoomLight = new Light("Living Room"); Light kitchenLight = new Light("Kitchen"); CeilLingFan ceilLingFan = new CeilLingFan("Living Room"); GarageDoor garageDoor = new GarageDoor(""); Stereo stereo = new Stereo("Living Room"); LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight); LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight); LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight); CeilLingFanLowCommand ceilingFanLow = new CeilLingFanLowCommand(ceilLingFan); CeilLingFanOffCommand ceilLingFanOff = new CeilLingFanOffCommand(ceilLingFan); GarageDoorOpenCommand garageDoorOpen = new GarageDoorOpenCommand(garageDoor); GarageDoorCloseCommand garageDoorClose = new GarageDoorCloseCommand(garageDoor); StereoOnWithCDCommand stereoOnWithCd = new StereoOnWithCDCommand(stereo); StereoOffCommand stereoOff = new StereoOffCommand(stereo); remoteControl.SetCommand(0, livingRoomLightOn, livingRoomLightOff); remoteControl.SetCommand(1, kitchenLightOn, kitchenLightOff); remoteControl.SetCommand(2, ceilingFanLow, ceilLingFanOff); remoteControl.SetCommand(3, garageDoorOpen, garageDoorClose); remoteControl.SetCommand(4, stereoOnWithCd, stereoOff); Console.WriteLine(remoteControl); remoteControl.OnButtonWasPushed(0); remoteControl.OffButtonWasPushed(0); remoteControl.OnButtonWasPushed(1); remoteControl.OffButtonWasPushed(1); remoteControl.OnButtonWasPushed(2); remoteControl.OffButtonWasPushed(2); remoteControl.OnButtonWasPushed(3); remoteControl.OffButtonWasPushed(3); remoteControl.OnButtonWasPushed(4); remoteControl.OffButtonWasPushed(4);*/ //多个插槽(含有关闭功能,含有撤销功能)的控制装置 /*RemoteControl remoteControl = new RemoteControl(); Light livingRoomLight = new Light("Living Room"); LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight); remoteControl.SetCommand(0, livingRoomLightOn, livingRoomLightOff); remoteControl.OnButtonWasPushed(0); remoteControl.OffButtonWasPushed(0); Console.WriteLine(remoteControl); remoteControl.UndoButtonWasPushed(); remoteControl.OffButtonWasPushed(0); remoteControl.OnButtonWasPushed(0); Console.WriteLine(remoteControl); remoteControl.UndoButtonWasPushed();*/ //多个插槽(含有状态对象的撤销功能)的控制装置 /*RemoteControl remoteControl = new RemoteControl(); CeilLingFan ceilLingFan = new CeilLingFan("Living Room"); CeilLingFanHighCommand ceilingFanHigh = new CeilLingFanHighCommand(ceilLingFan); CeilLingFanMediumCommand ceilLingFanMedium = new CeilLingFanMediumCommand(ceilLingFan); CeilLingFanOffCommand ceilLingFanOff = new CeilLingFanOffCommand(ceilLingFan); remoteControl.SetCommand(0, ceilLingFanMedium, ceilLingFanOff); remoteControl.SetCommand(1, ceilingFanHigh, ceilLingFanOff); remoteControl.OnButtonWasPushed(0); remoteControl.OffButtonWasPushed(0); Console.WriteLine(remoteControl); remoteControl.UndoButtonWasPushed(); remoteControl.OnButtonWasPushed(1); Console.WriteLine(remoteControl); remoteControl.UndoButtonWasPushed();*/ //多个插槽(含有宏命令)的控制装置 RemoteControl remoteControl = new RemoteControl(); Light light = new Light("Living Room"); TV tv = new TV("Living Room"); Stereo stereo = new Stereo("Living Room"); Hottub hottub = new Hottub(); LightOnCommand lightOn = new LightOnCommand(light); StereoOnCommand stereoOn = new StereoOnCommand(stereo); TVOnCommand tvOn = new TVOnCommand(tv); HottubOnCommand hottubOn = new HottubOnCommand(hottub); LightOffCommand lightOff = new LightOffCommand(light); StereoOffCommand stereoOff = new StereoOffCommand(stereo); TVOffCommand tvOff = new TVOffCommand(tv); HottubOffCommand hottubOff = new HottubOffCommand(hottub); ICommand[] partyOn = { lightOn, stereoOn, tvOn, hottubOn }; ICommand[] partyOff = { lightOff, stereoOff, tvOff, hottubOff }; MacroCommand partyOnMacro = new MacroCommand(partyOn); MacroCommand partyOffMacro = new MacroCommand(partyOff); remoteControl.SetCommand(0, partyOnMacro, partyOffMacro); Console.WriteLine(remoteControl); Console.WriteLine("-----Pushing Macro On-----"); remoteControl.OnButtonWasPushed(0); Console.WriteLine("=====Pushing Macro Off----"); remoteControl.OffButtonWasPushed(0); }
---------------------------------以上内容根据《head frist design mode》进行整理