初识设计模式 chapter 06-命令模式
2014-04-02 16:13
435 查看
初识设计模式 chapter 06-命令模式
1 引言
在本章,我们将把封装带到一个全新的境界:把方法调用(Method Invocation)封装起来。没错,通过封装方法调用,我们可以把运算块包装成形。所以调用此运算的对象不需要关心事情是如何进行的,只要知道如何使用包装成形的方法来完成它就可以。通过封装方法调用,也可以做一些很聪明的事情,例如记录日志,或者重复使用这些封装来实现撤销(undo)。
2 正文
2.1 命令模式可能行
本章的需求背景是:有一个电器公司要求开发一款遥控器可以控制家庭电器的开关、音量调节等功能。并且提供了一个可编程的遥控器,一共15个按钮:7对on/off按钮,一个全局撤销(undo)按钮。还有很多家庭电器类,而且以后还会增加。命令模式可将”动作的请求者”和“动作的执行者”对象中解耦。在你们的例子中,请求者可以是遥控器,而执行对象就是厂商类其中之一的实例。
在你的设计中采用“命令对象”。利用命令对象,把请求(例如打开电灯)封装成一个特定对象(例如客厅电灯对象)。所以,如果对每个按钮都存储一个命令对象,那么当按钮按下的时候,就可以请命令对象做相关的工作。遥控器并不需要知道工作内容是什么,只要有个命令对象能和正确的对象沟通,把事情做好就可以了。所以,看吧,遥控器和电灯对象解耦了。
回到餐厅模式,我们都知道餐厅是怎么工作的:
1、你,也就是顾客,把订单交给女招待。
2、女招待拿了订单,放在订单柜台,然后喊了一声“订单来了”。
3、快餐厨师根据订单准备餐点。
把餐厅想成是OO设计模式的一种模型,而这个模型允许将“发出请求的对象”和“接受与执行这些请求的对象”分离开来。比方说,对于遥控器API,我们需要分隔开“发出请求的按钮代码”和“执行请求的厂商特定对象”。万一遥控器的每个插槽都持有一个像餐厅订单那样的对象,会怎么样?那么,当一个按钮被按下,而遥控器不需要知道事情是怎么发生的,也不需要知道涉及哪些对象。
2.2 从餐厅到命令模式
实现命令接口Commandpublic interface Command { public void execute(); }
实现一个打开电灯的命令
//这是一个命令,所以需要实现Command接口 public class LightOnCommand implements Command { Light light; /* * 构造器被传入了某个电灯(比方说客厅的电灯),以便这个命令控制。 * 然后记录在实例变量中。一旦弟阿勇execute(),就由这个电灯对象成为接受者,负责接收请求。 */ public LightOnCommand(Light light) { this.light = light; } //这个execute()方法调用接受对象(我们正在控制的电灯)的on()方法 public void execute() { light.on(); } }
使用命令对象,假设我们有一个遥控器,它只有一个按钮和对应的插槽,可以控制一个装置:
// // This is the invoker // public class SimpleRemoteControl { //有一个插槽持有命令,而这个命令控制着一个装置 Command slot; public SimpleRemoteControl() {} //这个方法用来设置插槽控制的命令。如果这段代码的客户想要改变遥控器按钮的行为,可以多次调用这个方法。 public void setCommand(Command command) { slot = command; } //当按下这个按钮时,这个方法就会被调用,使得当前命令衔接插槽,并调用它的execute()方法 public void buttonWasPressed() { slot.execute(); } }
遥控器使用的简单测试
//这是命令模式的客户 public class RemoteControlTest { public static void main(String[] args) { //遥控器就是调用者,会传入一个命令对象,可以用来发出请求。 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(); } }
2.3 定义命令模式
命令模式,将“请求”封装成对象,以便使用不同的请求、队列、或者日志来参数化其他对象。命令模式也支持可撤销的操作。实现遥控器
// // This is the invoker // public class RemoteControl { //这时候,遥控器要处理7个开与关的命令,使用相应数组记录这些命令 Command[] onCommands; Command[] offCommands; public RemoteControl() { //在构造器中,只需要实例化并初始化两个开与关的数组 onCommands = new Command[7]; offCommands = new Command[7]; //NoCommand对象是一个空对象(null obeject)的例子。当你不想返回一个有意义的对象时,空对象就很有用,客户也可以将处理null的责任转义给空对象。举例来说,遥控器不可能一出厂就设置了有意义的命令对象,所以提供了NoCommand对象作为替代品,当调用它的execute() //方法时,这种对象什么也不做。 Command noCommand = new NoCommand(); for (int i = 0; i < 7; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } } /* * setCommand()方法须有三个参数,分别是插槽的位置、开的命令、关的命令。 * 这些命令将记录在开关数组中对应的插槽位置,以供稍后使用。 */ public void setCommand(int slot, Command onCommand, Command offCommand) { onCommands[slot] = onCommand; offCommands[slot] = offCommand; } //当按下开或关的按钮,硬件就会负责调用对应的方法,也就是onButtonWasPushed()或者offButtonWasPushed()。 public void onButtonWasPushed(int slot) { onCommands[slot].execute(); } public void offButtonWasPushed(int slot) { offCommands[slot].execute(); } //覆盖toString(),打印出每个插槽和它对应的命令。稍后在测试遥控器的时候,会用到这个方法。 public String toString() { StringBuffer stringBuff = new StringBuffer(); stringBuff.append("\n------ Remote Control -------\n"); for (int i = 0; i < onCommands.length; i++) { stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName() + " " + offCommands[i].getClass().getName() + "\n"); } return stringBuff.toString(); } }
实现命令
public class StereoOnWithCDCommand implements Command { Stereo stereo; public StereoOnWithCDCommand(Stereo stereo) { this.stereo = stereo; } //要实现这个请求,需要调用音响的三个方法:首先打开它,然后把它设置成播放CD,最后把音量设置为11。 public void execute() { stereo.on(); stereo.setCD(); stereo.setVolume(11); } }
逐步测试遥控器
public class RemoteLoader { public 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); GarageDoorUpCommand garageDoorUp = new GarageDoorUpCommand(garageDoor); GarageDoorDownCommand garageDoorDown = 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); //在这里,使用toString()方法打印出每个遥控器的插槽和它被指定的命令 System.out.println(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); } }
2.4 别忘了撤销
重新设计Command接口public interface Command { public void execute(); public void undo(); }
重新设计命令对象
public class LightOnCommand implements Command { Light light; int level; public LightOnCommand(Light light) { this.light = light; } public void execute() { level = light.getLevel(); light.on(); } public void undo() { light.dim(level); } }
重新设计遥控器
public class RemoteControlWithUndo { Command[] onCommands; Command[] offCommands; Command undoCommand; public RemoteControlWithUndo() { 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; } //初始化的时候,并没有所谓的“前一个命令”,所以将它设置为NoCommand对象。 undoCommand = noCommand; } public void setCommand(int slot, Command onCommand, Command offCommand) { onCommands[slot] = onCommand; offCommands[slot] = offCommand; } public void onButtonWasPushed(int slot) { onCommands[slot].execute(); /* * 当按下按钮,我们取得这个命令,并优先执行它然后将它记录在nudoConmmand实例变量中。 * 不管是开或者关命令,我们的处理方法都是一样的。 */ undoCommand = onCommands[slot]; } public void offButtonWasPushed(int slot) { offCommands[slot].execute(); undoCommand = offCommands[slot]; } public void undoButtonWasPushed() { //当按下撤销按钮,我们调用undoConmmand实例变量的Undo()方法,就可以倒转前一个命令。 undoCommand.undo(); } public String toString() { StringBuffer stringBuff = new StringBuffer(); stringBuff.append("\n------ Remote Control -------\n"); for (int i = 0; i < onCommands.length; i++) { stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName() + " " + offCommands[i].getClass().getName() + "\n"); } stringBuff.append("[undo] " + undoCommand.getClass().getName() + "\n"); return stringBuff.toString(); } }
2.5 宏命令
public class MacroCommand implements Command { Command[] commands; public MacroCommand(Command[] commands) { this.commands = commands; } //当这个宏命令被遥控器执行时,就会一次性执行数组里的每个命令 public void execute() { for (int i = 0; i < commands.length; i++) { commands[i].execute(); } } public void undo() { for (int i = commands.length -1; i >= 0; i--) { commands[i].undo(); } } }
2.6 命令模式的高级用途
队列请求和日志请求,关于这两个高级用途实例请阅读其他材料,本书中只略微带过,不过这才是命令模式在实际应用中大展拳脚的地方,后面有机会再深入研究。3 本章小结
玩过游戏的朋友肯定对宏这个东西不陌生,君不见wower必备的焦点宏,喊话宏,其实都是由一个个命令组成。命令模式在实际项目中的运用也是较多,各位coder有哪些实际经历呢?相关文章推荐
- 初识PHP设计模式--命令模式
- 初识设计模式 chapter 09-迭代器与组合模式
- 初识设计模式 chapter 02-观察者模式
- 初识设计模式 chapter 05-单件模式
- 初识设计模式 chapter 07-适配器模式与外观模式
- 初识设计模式 chapter 04-工厂模式
- 初识设计模式 chapter 01-策略模式
- 初识设计模式 chapter 08-模板方法模式
- 初识设计模式 chapter 10-状态模式
- 设计模式06-命令模式
- 初识设计模式 chapter 11-代理模式
- 初识设计模式 chapter 03-装饰者模式
- 设计模式——命令模式
- 设计模式之命令模式(Command)
- 设计模式(18)-命令模式(Command)
- 设计模式——命令模式
- php设计模式专题附源码(适配器模式、模板模式、命令模式、单例模式、观察者模式)
- 架构设计六之命令模式
- 设计模式:命令模式(Command)
- 设计模式 之 命令(command)模式