当一个请求者需要执行多种类的类似的方法时,如果考虑这些类的具体实现的方法,调用的过程就会变得异常困难:请求者不得不获取每一个可能被调用的类的实例变量,并且在执行方法时需要在这些实例中寻找。
例如:请求者是遥控器,执行者是家电。遥控器不需要知晓每个家电的具体功能就可以令其执行各种功能。
为了解耦请求者和执行者,可以把请求者的请求封装为一系列的命令对象,请求者存储不同的命令,但不知道命令如何被执行者执行,只能从接口中获取命令的信息。
利用命令对象就成功地将请求者和执行者分隔开,由命令对象代替请求者与执行者交互,命令对象只知道执行命令需要调用执行者的哪些方法,也不需要知晓执行者的实现细节。
继续以遥控器为例:遥控器上有一组按钮,每个按钮存储一个命令对象。当按钮被按下时,遥控器调用相应命令对象的执行命令方法,命令对象根据自己存储的家电引用,调用其中的某些方法来执行命令。
package command;
// 遥控器类
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 onButtonWasPressed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
// 发出命令
public void offButtonWasPressed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
// 发出撤销命令
public void undoButtonWasPressed() {
undoCommand.undo();
}
@Override
public String toString() {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("\n------ Remote Control -------\n");
for (int i = 0; i < onCommands.length; i++) {
stringBuffer.append("[slot " + i + "] " + onCommands[i].getClass().getName()
+ " " + offCommands[i].getClass().getName() + "\n");
}
return stringBuffer.toString();
}
}
package command;
// 命令类
public interface Command {
// 简洁的接口
// 执行命令
public void execute();
// 撤销命令
public void undo();
}
package command;
// 以风扇为例
public class CeilingFan {
// 风扇速度定义
public static final int HIGH = 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;
System.out.println(location + " ceiling fan is on high");
}
public void medium() {
speed = MEDIUM;
System.out.println(location + " ceiling fan is on medium");
}
public void low() {
speed = LOW;
System.out.println(location + " ceiling fan is on low");
}
public void off() {
speed = OFF;
System.out.println(location + " ceiling fan is off");
}
// 获取风扇当前速度
public int getSpeed() {
return speed;
}
}
package command;
// 风扇高速命令类
public class CeilingFanHighCommand implements Command {
// 存储一个实际执行该命令的风扇实例
CeilingFan ceilingFan;
// 保存之前的风扇速度
int prevSpeed;
public CeilingFanHighCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
}
// 调用具体的风扇的方法来执行命令
@Override
public void execute() {
prevSpeed = ceilingFan.getSpeed();
ceilingFan.high();
}
// 调用具体的风扇方法来执行撤销命令
@Override
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();
}
}
}
以上代码表示了风扇和遥控器之间的控制关系,遥控器只能通过按下自己的按钮来发出相应的命令请求,甚至只能通过命令的变量名称来确认具体是由哪个执行者执行了哪种命令;而命令对象只知道自己应当调用执行者提供的哪些方法来执行命令,并不知道执行者具体如何执行命令;执行者则仅知道每个具体方法的执行细节,甚至不知道是哪个命令对象调用了它的方法。
如此一来,遥控器和风扇就被成功地解耦了,遥控器按下按钮就可以期待接口所提供的结果了。
有时候,请求者想要执行一整个事件(由多个固定的命令组合)而不是单个命令,此时可以定义一个宏命令,这个命令包含了一系列的命令,但只占用一个按钮。按下宏命令按钮,就可以一次性发出多个请求。
package command;
// 宏命令同样实现命令接口
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 = commands.length-1; i >= 0; i--) {
commands[i].undo();
}
}
}
原文:https://www.cnblogs.com/aries99c/p/12836692.html