命令模式把“一次要执行的操作”封装成对象。调用端只持有命令接口,不必知道具体设备或业务逻辑怎样实现。
遥控器不必识别每一种设备
如果按钮里不断增加“是电灯就开灯,是门就开门”的分支,每接入一种设备都要修改遥控器。可以把操作移到命令对象:
interface Command {
void execute();
}
final class Light {
private boolean on;
void on() { on = true; }
void off() { on = false; }
boolean isOn() { return on; }
}
final class LightOnCommand implements Command {
private final Light light;
LightOnCommand(Light light) { this.light = light; }
public void execute() { light.on(); }
}
final class RemoteControl {
private Command command;
void setCommand(Command command) { this.command = command; }
void press() {
if (command != null) command.execute();
}
}
这里 Light 是接收者,LightOnCommand 是具体命令,RemoteControl 是调用者。通过 setCommand 更换命令,同一个按钮就可以触发不同操作。增加关灯或开门命令,不必让按钮了解每种设备的实现。
这是关系演示,不包含并发、持久化队列或外部设备通信。
撤销不是再执行一次
假设命令是“计数加一”,重复 execute 会再加一,而不是恢复。撤销需要定义逆操作或保存执行前状态。
即使是开灯,也不一定能把撤销简单写成关灯:如果执行前灯本来就亮着,恢复原状应该仍然亮着。因此,支持 undo 的命令还要决定保存什么状态、能否多次撤销,以及失败时怎么办。
涉及已发出的通知、已完成的支付等外部副作用时,通常无法让事实消失,只能设计业务补偿。
日志与重放也有自己的契约
重放需要可恢复的操作参数、版本、执行顺序和状态;重复执行还可能要求幂等或去重。打印一行命令名称,并不足以支持故障恢复。
命令模式提供的是操作封装与调用解耦。是否可以排队、撤销、记录或重放,取决于进一步设计的接口和执行机制,不能从一个 execute 方法自动得到这些能力。