想要一个对象执行某个任务?将任务封装成命令,传递给接收者,这样任务就能灵活排队、撤销,甚至日志记录。

在日常生活中,我们常常会遇到需要委托别人执行某项任务的场景。比如,老板把一个任务委托给下属,下属会根据任务内容去执行。不同的是,在命令模式中,我们将任务封装为一个对象,并将这个对象传递给接收者,接收者根据命令执行相应操作。这种方式有很多好处,比如:你可以灵活地排队执行任务、撤销操作,甚至把执行的日志都记录下来。

趣味解读:遥控器指挥家!

想象一下,你坐在沙发上,手里拿着一个遥控器,而电视、音响、灯光等家电都被你远程操控。遥控器上的每个按钮都对应着一个任务,比如打开电视、调高音量、调整灯光亮度等。每按一个按钮,遥控器就会发送一个指令给相应的设备。设备接收到命令后,执行相应的操作。

这就是命令模式:遥控器相当于命令对象,家电设备就是命令的接收者。你只需要通过遥控器发出命令,而不需要关心家电内部是如何实现的。命令可以排队执行、撤销或者记录日志,极大地方便了操作和管理。

Java代码案例:命令模式 - 电视遥控器

假设我们正在设计一个简单的电视遥控器系统,用户可以通过遥控器开关电视、调节音量等。每个操作被封装为一个命令对象,遥控器通过调用这些命令来执行任务。

// 1. 命令接口
interface Command {
    void execute();  // 执行命令
    void undo();     // 撤销命令
}
​
// 2. 具体命令 - 开电视
class TurnOnTVCommand implements Command {
    private TV tv;
​
    public TurnOnTVCommand(TV tv) {
        this.tv = tv;
    }
​
    @Override
    public void execute() {
        tv.turnOn();
    }
​
    @Override
    public void undo() {
        tv.turnOff();
    }
}
​
// 3. 具体命令 - 关电视
class TurnOffTVCommand implements Command {
    private TV tv;
​
    public TurnOffTVCommand(TV tv) {
        this.tv = tv;
    }
​
    @Override
    public void execute() {
        tv.turnOff();
    }
​
    @Override
    public void undo() {
        tv.turnOn();
    }
}
​
// 4. 具体命令 - 调节音量
class SetVolumeCommand implements Command {
    private TV tv;
    private int volume;
​
    public SetVolumeCommand(TV tv, int volume) {
        this.tv = tv;
        this.volume = volume;
    }
​
    @Override
    public void execute() {
        tv.setVolume(volume);
    }
​
    @Override
    public void undo() {
        tv.setVolume(10);  // 恢复音量为默认值
    }
}
​
// 5. 接收者 - TV(电视机)
class TV {
    public void turnOn() {
        System.out.println("电视机打开了");
    }
​
    public void turnOff() {
        System.out.println("电视机关了");
    }
​
    public void setVolume(int volume) {
        System.out.println("电视音量设置为: " + volume);
    }
}
​
// 6. 请求者 - 遥控器
class RemoteControl {
    private Command command;
​
    public void setCommand(Command command) {
        this.command = command;
    }
​
    public void pressButton() {
        command.execute();
    }
​
    public void pressUndo() {
        command.undo();
    }
}
​
// 7. 客户端代码
public class Main {
    public static void main(String[] args) {
        TV tv = new TV();
​
        Command turnOnTV = new TurnOnTVCommand(tv);
        Command turnOffTV = new TurnOffTVCommand(tv);
        Command setVolume = new SetVolumeCommand(tv, 20);
​
        RemoteControl remote = new RemoteControl();
​
        // 开电视
        remote.setCommand(turnOnTV);
        remote.pressButton();
​
        // 调节音量
        remote.setCommand(setVolume);
        remote.pressButton();
​
        // 关电视
        remote.setCommand(turnOffTV);
        remote.pressButton();
​
        // 撤销上一步操作
        remote.pressUndo();
​
        // 撤销上一步操作
        remote.pressUndo();
    }
}

解析:

  • 命令接口Command 定义了两个方法:execute() 用来执行命令,undo() 用来撤销命令。

  • 具体命令:每个具体的命令类(如 TurnOnTVCommandTurnOffTVCommandSetVolumeCommand)都实现了 Command 接口,并在 execute() 中执行相应操作,在 undo() 中执行撤销操作。

  • 接收者TV 类代表电视机,它有控制开关、调节音量等功能。

  • 请求者RemoteControl 类代表遥控器,它持有一个命令对象并触发命令的执行。

运行结果:

电视机打开了
电视音量设置为: 20
电视机关了
电视机打开了
电视机关了

实际应用场景:

  1. GUI操作:在图形用户界面应用中,用户的每一个操作(比如点击按钮、选择菜单项)都可以视为一个命令。通过命令模式,我们可以把这些操作封装为对象,从而实现任务的排队、撤销或日志记录等功能。

  2. 撤销与重做:命令模式非常适合实现撤销和重做功能。每个操作都被封装成一个命令对象,执行撤销时,命令对象只需调用 undo() 方法即可撤销操作。

  3. 日志记录与事务管理:命令模式可以帮助我们记录操作日志,将每个命令的执行都记录下来。系统可以根据日志回放或者重新执行操作,便于事务管理。

  4. 宏命令:通过将多个命令对象组合成一个复合命令(宏命令),我们可以批量执行多个操作。例如,在自动化测试中,我们可以将多个测试步骤封装为一个命令对象,依次执行每个步骤。

总结:

命令模式通过将请求封装成命令对象,使得请求的发送者与接收者解耦。命令对象不仅可以灵活地排队执行,还可以支持撤销和重做等功能。在复杂的系统中,命令模式帮助我们更好地管理请求,提高了代码的灵活性和可扩展性。