【设计模式】【行为型模式】命令模式(Command)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD

🔥 2025本人正在沉淀中... 博客更新速度++

📫 欢迎+V: flzjcsg2,我们共同讨论Java深渊的奥秘

🎵 当你的天空突然下了大雨,那是我在为你炸乌云

文章目录

一、入门

什么是命令模式?

命令模式是一种行为设计模式,它将请求或操作封装为对象,从而使你可以用不同的请求对客户进行参数化,并支持请求的排队、记录、撤销等操作。

命令模式的核心 是将"请求"封装为独立的对象,包含执行操作所需的所有信息。这样,你可以将请求与执行者解耦,并通过参数化、队列或日志等方式管理请求。

为什么需要命令模式?

在没有使用命令模式的情况下,代码可能会遇到以下问题:

  • 紧耦合
    • 调用者(Invoker)直接依赖接收者(Receiver)的具体实现。如果接收者的接口或行为发生变化,调用者也需要修改。
    • 例如,一个按钮直接调用某个对象的特定方法,导致按钮代码与具体逻辑紧密耦合。
  • 难以扩展
    • 如果需要添加新的操作,必须修改调用者的代码,违反了开闭原则(对扩展开放,对修改关闭)。
    • 例如,一个遥控器需要支持新的设备时,必须修改遥控器的代码。
  • 不支持撤销、重做或事务操作
    • 如果系统需要支持撤销、重做或事务操作,直接调用方法的方式难以实现这些功能。
    • 例如,一个文本编辑器需要支持撤销操作,直接调用方法的方式无法记录历史状态。
  • 难以实现请求的队列或日志
    • 如果需要对请求进行排队、延迟执行或记录日志,直接调用方法的方式无法实现这些功能。

怎样实现命令模式?

命令模式的组成:

命令(Command) :定义执行操作的接口,通常包含一个 execute() 方法。

具体命令(Concrete Command) :实现命令接口,负责调用接收者的操作。

接收者(Receiver) :实际执行操作的对象。

调用者(Invoker) :持有命令对象,并触发命令的执行。

客户端(Client):创建命令对象并设置其接收者。

【案例】 开关灯

Light(接收者) :实际执行操作的对象。包含 on()off()方法。'

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

    public void off() {
        System.out.println("Light is OFF");
    }
}

Command(命令接口) :定义执行操作的接口,包含 execute()方法。

java 复制代码
interface Command {
    void execute();
}

LightOnCommandLightOffCommand(具体命令) :实现 Command 接口,封装了对 Light 的操作。持有 Light 对象的引用,并在 execute() 方法中调用 Light 的方法。

java 复制代码
// 具体命令:开灯
class LightOnCommand implements Command {
    private Light light;

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

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

// 具体命令:关灯
class LightOffCommand implements Command {
    private Light light;

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

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

RemoteControl(调用者) :持有 Command 对象的引用。通过pressButton()方法触发命令的执行。

java 复制代码
class RemoteControl {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void pressButton() {
        command.execute();
    }
}

CommandPatternDemo(客户端):创建接收者、命令对象和调用者,并将它们组装在一起。

java 复制代码
// 客户端
public class CommandPatternDemo {
    public static void main(String[] args) {
        // 创建接收者
        Light light = new Light();

        // 创建命令对象
        Command lightOn = new LightOnCommand(light);
        Command lightOff = new LightOffCommand(light);

        // 创建调用者
        RemoteControl remote = new RemoteControl();

        // 执行开灯命令
        remote.setCommand(lightOn);
        remote.pressButton();

        // 执行关灯命令
        remote.setCommand(lightOff);
        remote.pressButton();
    }
}

二、命令模式在源码中的运用

2.1、JDK的Runnable接口

Java 中的 Runnable 接口是命令模式的一个典型例子。

命令接口(类似于 Command 接口):Runnable接口。

java 复制代码
public interface Runnable {
    void run();
}

具体命令(类似于 ConcreteCommand) :我们自己实现的task,MyTask

java 复制代码
public class MyTask implements Runnable {

	// 在这里可以加接收这
	
    @Override
    public void run() {
        System.out.println("Task is running");
    }
}

调用者(类似于 Invoker) : Thead类,下面是简化版

java 复制代码
public class Thread {
    private Runnable target;

    public Thread(Runnable target) {
        this.target = target;
    }

    public void start() {
        task.run();
    }
}

客户端

java 复制代码
public class Main {
    public static void main(String[] args) {
        Runnable task = new MyTask(); // 创建具体命令
        Thread thread = new Thread(task); // 设置命令
        thread.start(); // 执行命令
    }
}

2.2、Spring 的 CommandLineRunner 接口

CommandLineRunner 是 Spring 框架中一个非常有用的接口,通常用于在 Spring Boot 应用启动后执行一些初始化任务或自定义逻辑。它本质上是命令模式的一个典型应用,将"启动时需要执行的任务"封装为一个命令对象,并由 Spring Boot 在合适的时机统一执行。
CommandLineRunner 的作用:CommandLineRunner 接口的主要作用是在 Spring Boot 应用启动完成后,执行一些额外的逻辑。例如:初始化数据、加载配置文件、启动后台任务、执行一些检查或测试逻辑。
命令接口(类似于 Command 接口)CommandLineRunner

java 复制代码
@FunctionalInterface
public interface CommandLineRunner {
    void run(String... args) throws Exception;
}

具体命令(类似于 ConcreteCommand)MyStartupTask

java 复制代码
@Component // 将类注册为 Spring Bean
public class MyStartupTask implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("Executing startup task...");

        // 打印命令行参数
        System.out.println("Command line arguments:");
        for (String arg : args) {
            System.out.println(arg);
        }

        // 执行自定义逻辑
        initializeDatabase();
        loadConfiguration();
    }

    private void initializeDatabase() {
        System.out.println("Initializing database...");
        // 初始化数据库的逻辑
    }

    private void loadConfiguration() {
        System.out.println("Loading configuration...");
        // 加载配置文件的逻辑
    }
}

调用者(类似于 Invoker) : SpringApplication,下面的代码是简化版

java 复制代码
public class SpringApplication {
    public void run(String... args) {
        // 初始化 Spring 上下文
        ConfigurableApplicationContext context = createApplicationContext();
        refreshContext(context);

        // 调用 CommandLineRunner
        callRunners(context, args);
    }

    private void callRunners(ApplicationContext context, String[] args) {
        // 获取所有 CommandLineRunner 的 Bean
        Map<String, CommandLineRunner> runners = context.getBeansOfType(CommandLineRunner.class);

        // 按顺序执行
        List<CommandLineRunner> sortedRunners = new ArrayList<>(runners.values());
        AnnotationAwareOrderComparator.sort(sortedRunners);

        // 调用每个 CommandLineRunner 的 run() 方法
        for (CommandLineRunner runner : sortedRunners) {
            runner.run(args);
        }
    }
}

客户端

java 复制代码
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

三、总结

命令模式的优点

  • 解耦调用者和接收者:
    • 调用者(Invoker)不需要知道具体的接收者(Receiver)是谁,只需要调用命令对象的 execute() 方法。
    • 降低了系统的耦合度,使得调用者和接收者可以独立变化。
  • 支持扩展:
    • 可以轻松添加新的命令类,而不需要修改调用者的代码。
    • 符合开闭原则(对扩展开放,对修改关闭)。
  • 支持撤销和重做:
    • 命令对象可以记录状态,从而支持撤销(undo)和重做(redo)操作。
    • 例如,文本编辑器可以通过命令对象记录每次操作的状态,从而实现撤销功能。
  • 支持请求的队列或日志:
    • 命令对象可以被排队、延迟执行或记录日志。
    • 例如,可以将命令对象放入队列中,按顺序执行,或者将命令对象记录到日志中以便后续重放。
  • 支持事务操作:
    • 可以将多个命令组合成一个复合命令,实现事务操作。
    • 例如,在数据库操作中,可以将多个更新操作封装为一个事务。

命令模式的缺点

  • 类的数量增加:
    • 每个命令都需要一个具体的类,可能导致类的数量增多。
    • 对于简单的操作,使用命令模式可能会显得过于繁琐。
  • 复杂性增加:
    • 对于简单的请求,直接调用方法可能更直观,使用命令模式会增加额外的复杂性。
    • 需要额外的代码来管理命令对象(如队列、日志等)。

命令模式的使用场景

  • 需要解耦调用者和接收者:
    • 当调用者不需要知道接收者的具体实现时,可以使用命令模式。
    • 例如,GUI 中的按钮点击事件、远程调用的请求处理等。
  • 需要支持撤销、重做或事务操作:
    • 当系统需要支持撤销、重做或事务操作时,命令模式是一个很好的选择。
    • 例如,文本编辑器、绘图软件、数据库事务等。
  • 需要将请求排队或记录日志:
    • 当需要对请求进行排队、延迟执行或记录日志时,可以使用命令模式。
    • 例如,任务调度系统、消息队列、操作日志等。
  • 需要支持扩展:
    • 当系统需要支持新的操作,而不希望修改现有代码时,可以使用命令模式。
    • 例如,遥控器支持新的设备、插件系统等。

参考

黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)_哔哩哔哩_bilibili

相关推荐
智想天开36 分钟前
15.命令模式:思考与解读
设计模式·命令模式
码傻啦弟40 分钟前
常用设计模式在 Spring Boot 项目中的实战案例
java·spring boot·后端·设计模式
吃面必吃蒜1 小时前
前端实战中的单例模式:以医疗药敏管理为例
前端·javascript·单例模式·设计模式
18你磊哥4 小时前
泛型设计模式实践
设计模式
全栈凯哥6 小时前
备忘录模式(Memento Pattern)详解
java·设计模式·备忘录模式
不当菜虚困1 天前
JAVA设计模式——(十)抽象工厂模式(Abstract Factory Pattern)
java·设计模式·抽象工厂模式
还是大剑师兰特1 天前
vue源代码采用的设计模式分解
javascript·vue.js·设计模式
数智研发说2 天前
AI技术下研发体系重构
人工智能·科技·设计模式·重构·交互·设计规范
今日上上签07072 天前
《OmniMeetProTrack 全维会议链智能追录系统 软件设计文档》
人工智能·设计模式·aigc·软件工程·团队开发·需求分析·规格说明书