Java命令模式源码剖析及使用场景

命令模式

一、原理与通俗理解

命令模式将请求封装为一个命令对象,将发出请求的对象与执行请求的对象解耦。命令模式可以让你在不同时间点调用命令,将命令放入队列中,并实现对命令的撤销和恢复操作。

比如在餐馆点餐,你(调用者)跟服务员(调用对象)说要来一份炒饭(命令),服务员就去通知厨师(执行对象)去炒一份炒饭。

二、项目开发日志功能

需求:记录日志并支持撤销操作

实现:

  1. 定义命令接口Command,定义执行命令、撤销命令方法
  2. LogCommand 类实现了具体的日志记录命令,记录日志时备份当前日志,并在撤销时恢复到备份的日志状态。
  3. LogCommandInvoker 类负责执行和管理命令,执行命令时记录历史记录,并且支持撤销操作。
  4. LogManager 类负责实际的日志记录操作,包括记录日志、获取当前日志内容以及清空日志。
java 复制代码
// 1. 定义命令接口
interface Command {
    void execute(); // 执行命令
    void undo(); // 撤销命令
}

// 2. 实现具体的日志记录命令
class LogCommand implements Command {
    private LogManager logManager;
    private String logMessage;
    private String backupLog; // 用于撤销操作

    public LogCommand(LogManager logManager, String logMessage) {
        this.logManager = logManager;
        this.logMessage = logMessage;
    }

    @Override
    public void execute() {
        backupLog = logManager.getLog(); // 备份当前日志
        logManager.log(logMessage); // 记录新日志
    }

    @Override
    public void undo() {
        logManager.clearLog(); // 清空当前日志
        logManager.log(backupLog); // 恢复到执行之前的日志
    }
}

// 3. 定义命令调用者
class LogCommandInvoker {
    private List<Command> commandHistory = new ArrayList<>();

    public void executeCommand(Command command) {
        command.execute(); // 执行命令
        commandHistory.add(command); // 将命令添加到历史记录中
    }

    public void undoCommand() {
        if (!commandHistory.isEmpty()) {
            Command command = commandHistory.remove(commandHistory.size() - 1); // 从历史记录中取出最后一个命令
            command.undo(); // 执行撤销操作
        }
    }
}

// 4. 定义命令执行者
class LogManager {
    private StringBuilder log = new StringBuilder();

    public void log(String message) {
        log.append(message).append("\n"); // 记录日志信息
        System.out.println("Logged: " + message);
    }

    public String getLog() {
        return log.toString(); // 获取当前日志内容
    }

    public void clearLog() {
        log = new StringBuilder(); // 清空日志
    }
}

使用:

java 复制代码
LogManager logManager = new LogManager();
LogCommandInvoker invoker = new LogCommandInvoker();

// 记录日志
invoker.executeCommand(new LogCommand(logManager, "Log message 1"));
invoker.executeCommand(new LogCommand(logManager, "Log message 2"));

// 撤销一条日志
invoker.undoCommand();

// 再次记录日志
invoker.executeCommand(new LogCommand(logManager, "Log message 3"));

三、Java源码中的命令模式

  1. java.lang.Runnable

Runnable接口允许将一个命令封装为一个可执行的对象,然后可以将该对象传递给线程执行。

java 复制代码
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        // 执行命令
    }
});
thread.start();

首先,我们来看一下Runnable接口的定义,这是一个简单的函数式接口,只有一个抽象方法run()。

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

现在我们创建一个类来实现Runnable接口:

java 复制代码
public class MyCommand implements Runnable {
    @Override
    public void run() {
        // 执行具体的任务
        System.out.println("Executing command");
    }
}

现在我们可以创建一个线程并将MyCommand对象作为参数传递给线程:

java 复制代码
public class Main {
    public static void main(String[] args) {
        MyCommand command = new MyCommand();

        Thread thread = new Thread(command);
        thread.start();  // 启动线程,调用command的run()方法
    }
}

在上面的例子中,MyCommand对象封装了需要被执行的任务,并且通过将其作为参数传递给线程,线程会调用其run()方法来执行具体的任务。这就是命令模式的应用,将操作封装成对象,并且能够在不同的上下文中执行这个命令。

因此,通过实现Runnable接口并重写run()方法,可以实现类似于命令模式的效果,封装命令并且能够在不同的上下文中执行。

四、总结优缺点以及使用经验

优点:

  1. 低耦合,命令发送者和执行者完全解耦
  2. 可以将命令存入队列,实现撤销/恢复操作
  3. 命令对象可以携带额外的执行信息
  4. 新增新命令非常方便,无需修改现有代码

缺点:

  1. 可能会导致系统有过多的具体命令类
  2. 命令对象本身冗长

使用经验:

  1. 适用于需要将操作请求作为对象进行参数化传递的场景
  2. 适用于需要支持命令队列、命令记录日志、撤销/恢复操作等功能的场景
  3. 可以考虑使用组合模式组合多个命令形成复合命令
  4. 在设计阶段就应该考虑是否需要支持撤销/恢复操作
  5. 命令模式可以为不同对象的相同操作提供统一的接口
  6. 在面向对象设计中,命令模式是常用的行为型设计模式

命令模式将请求与执行解耦,可以方便地扩展新的命令、实现命令队列和支持撤销/恢复操作等功能。在需要对操作进行参数化、队列化、日志记录、撤销/恢复等需求时,命令模式是一个不错的选择。

相关推荐
木头没有瓜6 分钟前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp
奋斗的老史6 分钟前
Spring Retry + Redis Watch实现高并发乐观锁
java·redis·spring
high20118 分钟前
【Java 基础】-- ArrayList 和 Linkedlist
java·开发语言
1nullptr11 分钟前
lua和C API库一些记录
开发语言·lua
Jerry Nan12 分钟前
Lua元表
开发语言·lua
老马啸西风15 分钟前
NLP 中文拼写检测纠正论文 C-LLM Learn to CSC Errors Character by Character
java
?3333318 分钟前
CTFHub Web进阶-PHP-Bypass disable_function攻略
开发语言·安全·web安全·php
所以经济危机就是没有新技术拉动增长了18 分钟前
二、javascript的进阶知识
开发语言·javascript·ecmascript
Bubluu29 分钟前
浏览器点击视频裁剪当前帧,然后粘贴到页面
开发语言·javascript·音视频
Cosmoshhhyyy37 分钟前
LeetCode:3083. 字符串及其反转中是否存在同一子字符串(哈希 Java)
java·leetcode·哈希算法