命令模式(Command Pattern)

命令模式(Command Pattern)是一种行为设计模式,它将一个请求封装为一个对象,从而使你可以使用不同的请求、队列或日志来参数化对象。命令模式让你可以在不修改调用对象的情况下将请求排队、记录日志或撤销操作。

核心思想:

  • 命令对象:将请求和其所有相关的参数封装到一个对象中。
  • 调用者:负责触发命令执行。
  • 接收者:负责执行具体的命令逻辑。
  • 优点:命令模式可以实现命令的撤销、重做、排队执行等功能。

关键组成:

  1. Command 接口:定义了一个执行命令的接口。
  2. 具体命令类 :实现了 Command 接口,负责将请求委托给接收者执行。
  3. 接收者:实际执行命令操作的对象。
  4. 调用者:通过命令对象来执行请求,不需要直接调用接收者。

场景示例:家庭自动化系统

假设我们有一个家庭自动化系统,能够通过遥控器控制各种设备,比如灯和音响。我们将通过命令模式封装打开/关闭设备的操作。

代码实现:

1. 创建命令接口 Command

Command 接口定义了所有命令都必须实现的 execute() 方法。

java 复制代码
public interface Command {
    void execute();
    void undo();  // 增加撤销功能
}
2. 创建接收者类

这些类代表具体的设备,如灯 (Light) 和音响 (Stereo),它们包含打开和关闭的具体操作。

2.1 Light
java 复制代码
public class Light {
    public void on() {
        System.out.println("The light is on");
    }

    public void off() {
        System.out.println("The light is off");
    }
}
2.2 Stereo
java 复制代码
public class Stereo {
    public void on() {
        System.out.println("The stereo is on");
    }

    public void off() {
        System.out.println("The stereo is off");
    }

    public void setCD() {
        System.out.println("CD is set in stereo");
    }

    public void setVolume(int volume) {
        System.out.println("Stereo volume set to " + volume);
    }
}
3. 创建具体的命令类

每个具体命令类都实现 Command 接口,将请求传递给对应的接收者对象执行。

3.1 LightOnCommand
java 复制代码
public class LightOnCommand implements Command {
    private Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }

    @Override
    public void undo() {
        light.off();
    }
}
3.2 LightOffCommand
java 复制代码
public class LightOffCommand implements Command {
    private Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }

    @Override
    public void undo() {
        light.on();
    }
}
3.3 StereoOnCommand
java 复制代码
public class StereoOnCommand implements Command {
    private Stereo stereo;

    public StereoOnCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    @Override
    public void execute() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(10);
    }

    @Override
    public void undo() {
        stereo.off();
    }
}
3.4 StereoOffCommand
java 复制代码
public class StereoOffCommand implements Command {
    private Stereo stereo;

    public StereoOffCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    @Override
    public void execute() {
        stereo.off();
    }

    @Override
    public void undo() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(10);
    }
}
4. 创建调用者类 RemoteControl

RemoteControl 作为调用者,保存命令对象并执行它们。

java 复制代码
public class RemoteControl {
    private Command[] onCommands;
    private Command[] offCommands;
    private Command undoCommand;

    public RemoteControl() {
        onCommands = new Command[7];
        offCommands = new Command[7];

        Command noCommand = new NoCommand(); // 空命令,避免 null 检查
        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();
    }
}
5. 创建空命令 NoCommand

空命令模式用于初始化每个命令插槽,防止出现 null 检查。

java 复制代码
public class NoCommand implements Command {
    @Override
    public void execute() {
        // 什么也不做
    }

    @Override
    public void undo() {
        // 什么也不做
    }
}
6. 测试客户端

客户端测试如何通过命令模式控制设备。

java 复制代码
public class RemoteControlTest {
    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl();

        // 创建接收者
        Light livingRoomLight = new Light();
        Stereo stereo = new Stereo();

        // 创建命令对象
        LightOnCommand lightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand lightOff = new LightOffCommand(livingRoomLight);
        StereoOnCommand stereoOn = new StereoOnCommand(stereo);
        StereoOffCommand stereoOff = new StereoOffCommand(stereo);

        // 将命令对象设置到遥控器中
        remoteControl.setCommand(0, lightOn, lightOff);
        remoteControl.setCommand(1, stereoOn, stereoOff);

        // 模拟按钮按下
        System.out.println("Turning on the light...");
        remoteControl.onButtonWasPressed(0);

        System.out.println("Turning off the light...");
        remoteControl.offButtonWasPressed(0);

        System.out.println("Undoing the last command...");
        remoteControl.undoButtonWasPressed();

        System.out.println("Turning on the stereo...");
        remoteControl.onButtonWasPressed(1);

        System.out.println("Turning off the stereo...");
        remoteControl.offButtonWasPressed(1);
    }
}

输出结果

plaintext 复制代码
Turning on the light...
The light is on
Turning off the light...
The light is off
Undoing the last command...
The light is on
Turning on the stereo...
The stereo is on
CD is set in stereo
Stereo volume set to 10
Turning off the stereo...
The stereo is off

代码解释

  1. 命令接口 Command :定义了命令的基本操作,包含 execute()undo() 方法。
  2. 具体命令类 :实现了 Command 接口,负责调用接收者执行具体的命令操作。每个命令类负责一个具体的任务,如打开或关闭灯、打开或关闭音响等。
  3. 接收者类 :如 LightStereo,这些类包含实际的业务逻辑。
  4. 调用者 RemoteControl :保存和调用命令对象,通过按钮调用 onCommandoffCommand
  5. 空命令 NoCommand:用来初始化按钮插槽,避免空指针异常。

优点

  • 松耦合:调用者和接收者之间通过命令对象解耦,调用者无需关心具体的业务实现。
  • 扩展性:可以轻松添加新的命令类而不影响其他类的代码。
  • 支持撤销和重做:命令模式通过封装命令对象,使得支持撤销和重做功能变得容易。

适用场景

  • 需要参数化对象以执行不同的请求。
  • 需要在不改变调用代码的情况下记录命令、撤销或排队执行命令。
  • 需要将操作封装为对象以便进行传递、记录、或回放。

通过命令模式,可以灵活地对操作进行解耦,适用于复杂系统中对操作的管理。

相关推荐
南山十一少1 小时前
Spring Security+JWT+Redis实现项目级前后端分离认证授权
java·spring·bootstrap
427724002 小时前
IDEA使用git不提示账号密码登录,而是输入token问题解决
java·git·intellij-idea
chengooooooo3 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
李长渊哦3 小时前
常用的 JVM 参数:配置与优化指南
java·jvm
计算机小白一个3 小时前
蓝桥杯 Java B 组之设计 LRU 缓存
java·算法·蓝桥杯
南宫生5 小时前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
计算机毕设定制辅导-无忧学长6 小时前
Maven 基础环境搭建与配置(一)
java·maven
风与沙的较量丶7 小时前
Java中的局部变量和成员变量在内存中的位置
java·开发语言
m0_748251727 小时前
SpringBoot3 升级介绍
java
极客先躯8 小时前
说说高级java每日一道面试题-2025年2月13日-数据库篇-请说说 MySQL 数据库的锁 ?
java·数据库·mysql·数据库的锁·模式分·粒度分·属性分