http://blog.sina.com.cn/s/blog_5016113a01009rta.html
命令模式定义
将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作
命令模式可以将“动作的请求者”和“动作的执行者”分隔开来(解耦)
例子:设计一个家电自动化遥控器的API。遥控器有七个插头,可以连接不同的家电电器,每个插头有对应的开关按钮,用来控制电器的开关。这个遥控器还具备一个整体的撤销按钮。
解析:
当遥控器按下“开”按钮时,直接调用家用电器的“开”方法。但是有很多家用电器,每种家用电器的“开”方法名称也不一定相同,这意味着每一个插头的“开”按钮按下时,都要判断是什么家用电器,然后再调用它的“开”方法。
使用命令模式:
封装方法调用,把方法调用封装成对象。把每种家用电器的 “开”方法和执行“开”方法的电器封装成一个统一标准的对象,遥控器通过操作统一标准的对象,来操作家用电器,就可以忽略每种家用电器之间的差异。
UML图(点击看大图)
UML图解
Light为电灯类,LightOnCommand封装了Light和Light的on()方法,它符合Command的标准。遥控器通过操作Command来操作家用电器,从而忽略多种家用电器之间的差异。
//1.Command接口形式:
public interface Command {
public void execute();
}
//2.LightOnCommand形式:
public class LightOnCommand implements Command {
Light light; //封装方法执行者
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on(); //封装方法
}
}
//3.SimpleRemoteControl遥控器形式如:
public class SimpleRemoteControl {
Command slot;
public SimpleRemoteControl() {}
public void setCommand(Command command) {
slot = command;
}
public void buttonWasPressed() {
slot.execute(); //遥控器不知道方法执行者到底是谁,它操作统一家用标准Command对象。
}
}
//4.实际动作方式:
SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand(light); //封装电灯“开”方法的对象。
remote.setCommand(lightOn); //把统一标准对象放入遥控器
remote.buttonWasPressed();
为遥控器实现撤销(undo)按钮
例子:遥控天花板吊扇,风扇有风速档位(low、medium、hight),并且要实现撤销按钮,即打开风扇,撤销后就关闭风扇,让风扇回到打开前状态。
UML图
UML图解
1.CeilingFan为天花板风扇类,包含开关、调档等方式
public class CeilingFan {
public static final int HIGH = 3; //风速档位为3档
public static final int MEDIUM = 2;
public static final int LOW = 1;
public static final int OFF = 0;
String location; //天花板的位置
int speed; //当前风速档位
public CeilingFan(String location) {
this.location = location;
speed = OFF;
}
public void high() {
speed = HIGH;
}
public void medium() {
speed = MEDIUM;
}
public void low() {
speed = LOW;
}
public void off() {
speed = OFF;
}
public int getSpeed() {
return speed;
}
}
2.Command接口,家用电器执行/撤销动作标准
public interface Command {
public void execute(); //执行某个动作
public void undo(); //撤销execute()要执行的动作
}
3.CeilingFanHighCommand类,包含风扇调为最高档位,和撤销到原来状态的方法
public class CeilingFanHighCommand implements Command {
CeilingFan ceilingFan;
int prevSpeed; //当前风速
public CeilingFanHighCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
}
public void execute() {
prevSpeed = ceilingFan.getSpeed(); //调为最高风速前,记下当时的风速,用于撤销动作
ceilingFan.high();
}
public void undo() { //撤销动作
if (prevSpeed == CeilingFan.HIGH) {
//如果先前是最高风速,撤销当前的动作,把风速调到最高。
ceilingFan.high();
} else if (prevSpeed == CeilingFan.MEDIUM) {
ceilingFan.medium();
} else if (prevSpeed == CeilingFan.LOW) {
ceilingFan.low();
} else if (prevSpeed == CeilingFan.OFF) {
ceilingFan.off();
}
}
}
4.RemoteControlWithUndo为遥控器类
public class RemoteControlWithUndo {
Command[] onCommands; //封装所有“开”命令,遥控器有7个开关
Command[] offCommands;
Command undoCommand; //当前的命令
public RemoteControlWithUndo() {
onCommands = new Command[7];
//初始化7个开与关命令,命令为noCommand对象,它什么都不做
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 onCommand, Command 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();
}
}
实际动作方式:
RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
CeilingFan ceilingFan = new CeilingFan("Living Room"); //在客厅天花板上的吊扇
CeilingFanMediumCommand ceilingFanMedium = new CeilingFanMediumCommand(ceilingFan);
//把风扇调到中档,这是个“开”命令
CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan); //关闭风扇命令
remoteControl.setCommand(0, ceilingFanMedium, ceilingFanOff);
remoteControl.onButtonWasPushed(0); //按下“开”
remoteControl.offButtonWasPushed(0); //按下“关”
remoteControl.undoButtonWasPushed(); //按下 “撤销”
NoCommand对象
NoCommand是一个空对象(null object),当你不想返回一个有意义的对象时,空对象就很有用。空对象可以避免我们在程序中写上判断对象是否为空的if等语句。
Party模式
按下一个按钮,就同时能打开音响、电灯、电视、设置好DVD,并让热水器开始加温。
设计一个“宏”命令类
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--)
for (int i = 0; i < commands.length; i++) {
commands[i].undo();
}
}
}
实际动作方式
RemoteControl remoteControl = new RemoteControl();
Command[] partyOn = {…………}; //一组“开”命令
Command[] partyOff = { ………...}; //一组“关”命令
MacroCommand partyOnMacro = new MacroCommand(partyOn); //初始化宏命令
MacroCommand partyOffMacro = new MacroCommand(partyOff);
remoteControl.setCommand(0, partyOnMacro, partyOffMacro);
remoteControl.onButtonWasPushed(0); //执行一组“开”命令
remoteControl.offButtonWasPushed(0);
如何学现多层次的撤销操作?
使用一个堆栈记录操作过程的每一个命令,按撤销时从堆栈(后进先出)中取出最上层的命令,调用它的undo方法。
命令模式的更多用途
1.队列请求
把一组命令放到队列(先进先出)中,线程从队列中一个一个删除命令,然后调用它的excecute()方法。
2.日志请求
把所有动作都记录在日志中,在系统发生错误时,重新调用这些动作恢复到之前的状态
Command Pattern的适用场景
1.使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。
2.需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。
3.系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。
4.如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
总结
Command模式是非常简单而又优雅的一种设计模式,它的根本目的在于将“行为请求者”与“行为实现者”解耦。