命令模式
场景描述:我们需要构建一个遥控器程序,它拥有几个插槽,而这些插槽可以配置不同功能的api,并且拥有一个撤销按钮,可以撤销上一次的操作。
第一构想:使用大段的if else做逻辑判断,但是这是一种非常糟糕的设计。
在提出改进方案之前,让我们看一个关于餐厅点餐的示例。
进而引申出命令模式
命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志参数化其他对象。命令模式也支持可撤销的操作。
一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。
我们也可以创建命令的宏,以便一次执行多个命令。
宏:批量处理。将一些命令组织在一起作为一个单独命令完成一个特定任务。
这是它的类图:
让我们回到之前的遥控器问题,我们尝试使用命令模式来实现它。
首先新建一个命令接口
public interface Command {
public void execute();
public void undo();
}
接着实现一个打开电灯的命令
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light){
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
而真正的执行者则是电灯
public class Light {
String location;
public Light(String location){
this.location = location;
}
public void on(){
System.out.println(location + " light is on");
}
public void off(){
System.out.println(location + " light is off");
}
}
所以我们的遥控器可以这么设置
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
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 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();
}
public String toString(){
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("\n------- Remote Control -------\n");
for (int i = 0; i < offCommands.length; i++){
stringBuffer.append("[slot " + i + "] " + onCommands[i].getClass().getName() + " " + offCommands[i].getClass().getName() + " \n");
}
return stringBuffer.toString();
}
}
NoCommand对象是一个空对象的例子。当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任转移给空对象。
public class NoCommand implements Command {
@Override
public void execute() {}
@Override
public void undo() {}
}
我们尝试进行一次测试,看看命令模式的效果
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);
StereoOffWithCDCommand stereoOffWithCD = new StereoOffWithCDCommand(stereo);
// remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
// remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
// remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
// remoteControl.setCommand(3, stereoOnWithCD, stereoOffWithCD);
//
// 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);
//测试undo
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
System.out.println(remoteControl);
remoteControl.undoButtonWasPushed();
System.out.println("--------------------------------------------------------");
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(0);
System.out.println(remoteControl);
remoteControl.undoButtonWasPushed();
}
}
接着我们尝试使用宏命令,这需要创建一个特殊的命令类。
public class MacroCommand implements Command {
Command[] commands;
public MacroCommand(Command[] commands){
this.commands = commands;
}
@Override
public void execute() {
for (int i = 0; i < commands.length; i++){
commands[i].execute();
}
}
@Override
public void undo() {
for (int i = 0; i < commands.length; i++){
commands[i].undo();
}
}
}
我们还是用之前的代码测试一下
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);
CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan);
CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);
StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);
StereoOffWithCDCommand stereoOffWithCD = new StereoOffWithCDCommand(stereo);
// 测试宏命令
Command[] partyOn = {livingRoomLightOn, stereoOnWithCD, ceilingFanOn};
Command[] partyOff = {livingRoomLightOff, stereoOffWithCD, ceilingFanOff};
MacroCommand partyOnMacro = new MacroCommand(partyOn);
MacroCommand partyOffMacro = new MacroCommand(partyOff);
remoteControl.setCommand(0, partyOnMacro, partyOffMacro);
System.out.println(remoteControl);
System.out.println("-------Pushing Macro On-------");
remoteControl.onButtonWasPushed(0);
System.out.println("-------Pushing Macro Off-------");
remoteControl.offButtonWasPushed(0);
}
}
如果我们想要实现多层次的撤销操作,可以使用一个堆栈记录操作过程的每一个命令,然后按下撤销按钮时,可以从堆栈中取出最上层的命令,然后执行它的undo()方法即可。
其他应用:工作队列
命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。甚至可以在不同的线程种被调用。因此我们可以利用这种特性衍生出一些应用,如:日程安排,线程池,工作队列等。
工作队列:你在一端添加命令,然后另一端则是线程。线程进行下面的动作:从队列中取出一个命令,调用它的execute()方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令。日志请求
某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态。通过新增两个方法(store()、load()),命令模式就能够支持这一点。
有许多调用大型数据结构的动作的应用无法在每次改变发生时快速地存储。通过使用记录日志,我们可以将上次检查点之后地所有操作记录下来,如果系统出状况,从检查点开始应用这些操作。
总结:命令模式的使用场景是:当我们需要将请求方法封装起来,而不需要知道具体是哪个实体执行了具体的请求时,就可以通过调用者来设置具体的命令实体,进而有具体的执行实体执行具体方法。
比如上述示例中,我们实现的遥控器就是一个调用者,而开灯关灯等命令就是具体的命令实体,在命令实体中包含了具体的执行实体,并通过它来执行具体方法。我们(即客户)只需使用遥控器(即调用者)的相关弹性方法即可。