设计模式 -- 命令模式(command)
1、什么是command命令模式?
command命令模式是“封装方法调用”的一个模式,通过封装方法调用,把运算块封装成形。那么调用此运算的对象就不必深究事情是如何进行的,只需要知道可以使用这个包装成型的方法来完成它就可以了。因为它把运算封装,而不关心具体对象和实现,因此命令模式在我们的系统中有很多应用,如队列请求,日志请求,撤消操作等等。
“封装方法调用”也可以这样理解,命令模式把“命令的请求者”和“命令的执行者”解耦。这样一来,当命令请求者在请求一个命令时不必关心执行此命令的是哪个对象,这个对象怎么执行这个命令这些细节问题。要实现这两者之间的解耦,必然的要多出一个“中间处理者”——命令,我们就把方法调用封装在这个结构里。更通俗的来讲一个例子,比如你去餐馆吃饭,服务员招待你,你点你要的东西,服务员会把这些内容记录成一个单子,这个单子就是命令,为什么呢?因为大厨必须根据这个单子来做菜,否则你是不会满意的。如果你认同我这种说法,那么就说明,你承认了“命令请求者”(你)和“命令执行者”之间解耦了。因为这之间,你不知道大厨是谁,也不知道他是怎么做的菜。
命令模式 -- 将请求封装成对象,这可以让你使用不同的请求,队列,或者日志请求来参数化其他对象,命令模式支持撤销操作。
2、如何实现“命令模式”模式?
实现命令模式,需要有一个“命令”接口(command),这个接口定义了一些列的命令,比如execute,undo等。你可以实现这个接口,那么你就有了一些具体的命令了。这个“命令”(command )接口就是我刚才说的“中间处理者”。命令的请求者在请求命令时,只需要指明需要的具体命令,而这个具体命令就必须维护自己的命令执行者,这样三者之间就发生了联系。看看下面的类图:
来解释这个类图:
Command:(可以是接口或者抽象类)为所有命令声明了接口。调用命令对象的execute方法就可以让接受者进行相关动作。
Concretecommand:定义了动作和接收者之间的绑定关系。调用者只要调用execute就可以发出请求,然后由Concretecommand调用接收者的一个或多个动作。
Client:(通常是我们的测试程序)负责创建一个ConcreteCommand,并设置其接收者。
Invoker:调用者,他要维护自己的命令对象,并在某个时间点调用命令对象的execute方法,将请求付诸实行。
Receiver:接收者,它知道如何进行必要的工作来实现这个请求。
3、具体情境分析
遥控器有很多按钮,我们打算将遥控器的每个按钮对应到一个命令,这样让遥控器成为“调用者”。当按下按钮,相应命令对象的execute方法会执行被调用,其结果就是,接收者(如电灯,车库门,吊扇)的动作被调用。
类图:
代码:
//命令接收者 -- 天花板 public class CeilingFan { public string position; public CeilingFan(string aPosition) { this.position = aPosition; } public void on() { Console.WriteLine("the ceilingfan is on"); } public void off() { Console.WriteLine("the ceilingfan is off"); } }
//命令接收者 -- 电灯类 public class Light { public string position;//指明灯的位置 public Light(string aName) { this.position = aName; } public void on() { Console.WriteLine("电灯打开了"); } public void off() { Console.WriteLine("电灯关上了"); } }
//命令接收者 -- 车库门 public class GarageDoor { public void up() { Console.WriteLine("门向上开启"); } public void down() { Console.WriteLine("门向下关闭"); } public void stop() { Console.WriteLine("门停止"); } public void lightOn() { Console.WriteLine("打开车库门"); } public void lightOff() { Console.WriteLine("车库门关上"); } }
//命令接收者 -- 音响类 public class Stereo { public string position; public Stereo(string aposition) { this.position = aposition; } public void on() { Console.WriteLine("音响打开了"); } public void off() { Console.WriteLine("音响关上了"); } public void setCD() { Console.WriteLine("CD就绪"); } public void setVolume(int num) { Console.WriteLine("音响有"); Console.WriteLine(num); Console.WriteLine("首歌"); } public void setRadio() { Console.WriteLine("音响录音"); } }
//命令请求者 -- 遥控器 public class RemoteControl { public Command[] onCommands;//7个开命令 public Command[] offCommands;//7个关命令 public Command undoCommand;//前一个命令将被记录在这里 public RemoteControl() { onCommands = new Command[7]; offCommands = new Command[7]; Command noCommand = new NoCommand(); for (int i = 0; i < 7;i ++ ) { onCommands[i] = noCommand; offCommands[i] = noCommand; } undoCommand = noCommand;//一开始没有所谓的“前一个”命令 } //参数:插槽位置 开命令 关命令。这些命令记录在开关数组中对应插槽的位置上 public void setCommand(int slot, Command onComand, Command offCommand) { onCommands[slot] = onComand; offCommands[slot] = offCommand; } public void onButtonWasPushed(int slot) { if (onCommands[slot] != null) { onCommands[slot].execute(); undoCommand = onCommands[slot]; } } public void offButtonWasPushed(int slot) { if (offCommands[slot] != null) { offCommands[slot].execute(); undoCommand = offCommands[slot]; } } public void undoButtonWasPressed() { undoCommand.undo(); } public string toString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("\n------ Remote Control ------\n"); for (int i = 0; i < 7;i++ ) { stringBuilder.Append("[slot" + i + "]" + onCommands[i] + " " + offCommands[i] + "\n"); } return stringBuilder.ToString(); } }
//命令接口 public interface Command { void execute(); void undo();//撤销操作 } //打开电灯命令类 public class LightOnCommand:Command { Light light; public LightOnCommand(Light alight) { this.light = alight; } public void execute() { light.on(); } public void undo() { light.off(); } } //关闭电灯命令类 public class LightOffCommand:Command { public Light light; public LightOffCommand(Light alight) { this.light = alight; } public void execute() { light.off(); } public void undo() { light.on();//execute是关闭电灯,那么undo就是打开电灯 } }
其他的命令类都是大同小异的,在此省略了。
测试代码:
static void Main(string[] args) { //命令调用者 RemoteControl remoteControl = new RemoteControl(); //命令接收者 Light livingRoomLight = new Light("Living Room"); Light kitchenLight = new Light("Kitchen"); CeilingFan ceilingfan = new CeilingFan("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); CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingfan); CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingfan); GarageDoorOpenCommand garageDoorOpen = new GarageDoorOpenCommand(garagedoor); GarageDoorDownCommand garageDoorDowm = new GarageDoorDownCommand(garagedoor); StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo); StereoOffCommand stereoOff = new StereoOffCommand(stereo); //将命令对应到调用者的接口上 remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff); remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff); remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff); remoteControl.setCommand(3, stereoOnWithCD, stereoOff); remoteControl.setCommand(4, garageDoorOpen, garageDoorDowm); string str = remoteControl.toString(); Console.WriteLine(str); 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); //以下是撤销操作 Console.WriteLine("------------------\n"); remoteControl.undoButtonWasPressed(); remoteControl.offButtonWasPushed(4); remoteControl.onButtonWasPushed(4); remoteControl.undoButtonWasPressed(); Console.ReadKey(); } }
呵呵,好了,也许你发下了,我们这里的撤销操作只是一步撤销,而往往我们希望能够连续撤销而回到很久以前的一个状态去。实现多层次撤销,我们可以使用堆栈记录操作的每一个命令,然后,不管什么时候按下按钮,你都可以从堆栈中取出最上层命令,然后调用它的undo()方法。我们可以使用宏命令,将命令放在一个一个集合里,这样就可实现这个遥控器的“Party”模式,开让一个按钮一次性控制多个命令。