二十三种设计模式(十四)--命令模式

命令模式(Command)

当我们有一个功能完善的类VideoClass,能够实现视频转码, 视频缓存 等等实际功能.

此时调用者需要依赖用户输入的命令来执行VideoClass中的一个或几个方法函数.

直觉上, 我们会写一个switch-case语句来处理用户输入的命令, 并执行VideoClass中的对应方法, 简单的可以这么做, 但是当用户的命令批量输入, 且要我们记录用户输入的所有命令, 或者要将所有命令都队列化存储之后依次执行时, 我们需要将命令解耦出来, 封装成独立的类, 这就是命令模式.

假如我们有如下问题:

java 复制代码
public class CommandPattern {
    public static void main(String[] args) {
        String command = args[1]; // 用户输入的指令

        Robot robot = new Robot();
        Weapon weapon = new Weapon();

        // TODO:: 要实现用户批量输入的命令: 变身车辆-发射5次粒子炮-变身人类-发射3发子弹
        // TODO:: 延迟3分钟发射粒子炮

        switch (command) {
            case "car":
                robot.transToCar();
                // TODO:: 记录日志: 变身车辆
                // TODO:: 记录变身耗时
                // 其他操作...
            case "human":
                robot.transToHuman();
                // TODO:: 记录日志: 变身人类
                // TODO:: 记录变身耗时
                // 其他操作...
            case "bullet":
                weapon.fireBullet();
                // TODO:: 记录日志: 开火发射子弹
                // TODO:: 记录发射子弹数量
                // 其他操作...
            case "particle":
                weapon.fireParticleCannon();
                // TODO:: 记录日志: 开火发射粒子炮
                // TODO:: 记录发射粒子炮次数
                // 其他操作...
        }
    }
}

// 两个真正执行指令对应的功能的类
class Robot {
    public void transToCar() {
        System.out.println("[robot] 变身一辆车");
    }

    public void transToHuman() {
        System.out.println("[robot] 变身T800");
    }
}

class Weapon {
    public void fireBullet() {
        System.out.println("[武器] 发射子弹");
    }

    public void fireParticleCannon() {
        System.out.println("[武器] 发射粒子炮");
    }
}

代码中标注TODO的部分, 随着需求的扩展, 会变得越来越臃肿

以下是针对以上问题, 命令模式的实现

定义命令的统一接口, 所有的命令类都依据接口实现功能

java 复制代码
// 命令模式的核心, 定义通用的命令接口
interface Command {
    // 执行命令
    void execute();
    // 获取命令名称(用于日志)
    String getCommandName();
}

// 命令一:变身车辆(绑定Robot接收者)
class TransToCarCommand implements Command {
    private final Robot robot;
    // 附加:记录耗时、日志
    private long startTime;
    private long endTime;

    public TransToCarCommand(Robot robot) {
        this.robot = robot;
    }

    @Override
    public void execute() {
        startTime = System.currentTimeMillis();
        // 执行接收者的核心逻辑
        robot.transToCar();
        endTime = System.currentTimeMillis();
        // 附加逻辑:记录日志、耗时
        log();
    }

    @Override
    public String getCommandName() {
        return "变身车辆";
    }

    private void log() {
        System.out.printf("[日志] 命令:%s,执行时间:%s,耗时:%dms%n",
                getCommandName(), new Date(), endTime - startTime);
    }
}

// 命令二:发射子弹(支持参数:发射次数)
class FireBulletCommand implements Command {
    private final Weapon weapon;
    private final int times; // 发射次数(参数封装)
    private long startTime;
    private long endTime;

    public FireBulletCommand(Weapon weapon, int times) {
        this.weapon = weapon;
        this.times = times;
    }

    @Override
    public void execute() {
        startTime = System.currentTimeMillis();
        // 执行带参数的逻辑
        for (int i = 0; i < times; i++) {
            weapon.fireBullet();
        }
        endTime = System.currentTimeMillis();
        log();
    }

    @Override
    public String getCommandName() {
        return "发射子弹";
    }

    private void log() {
        System.out.printf("[日志] 命令:%s,次数:%d,执行时间:%s,耗时:%dms%n",
                getCommandName(), times, new Date(), endTime - startTime);
    }
}

// 命令三 ...
// 命令四 ...
// 命令五 ...
// 命令随着功能类的更新可以无限扩展

由于单独命令都进行了封装, 因此可以将命令队列化批量操作, 可以将命令记录日志

可以支持更灵活的操作

于是, 下面这个类实现将命令存储进队列后依次执行的功能

java 复制代码
// 请求者:命令调用器(管理命令队列,批量执行)
class CommandInvoker {
    // 命令队列(存储所有命令,支持批量执行)
    private final List<Command> commandQueue = new ArrayList<>();

    // 添加命令到队列
    public void addCommand(Command command) {
        commandQueue.add(command);
    }

    // 执行队列中的所有命令
    public void executeAll() {
        System.out.println("\n===== 开始执行命令队列 =====");
        for (Command command : commandQueue) {
            command.execute();
        }
        System.out.println("===== 命令队列执行完成 =====\n");
    }

    // 清空队列
    public void clearQueue() {
        commandQueue.clear();
    }
}

真正执行指令功能的类不修改

java 复制代码
import java.util.Date;
import java.util.ArrayList;
import java.util.List;

public class CommandPattern {
    public static void main(String[] args) {
        // 1. 创建接收者(真正干活的对象)
        Robot robot = new Robot();
        Weapon weapon = new Weapon();

        // 2. 创建请求者(命令调用器,管理队列)
        CommandInvoker invoker = new CommandInvoker();

        // 3. 客户端组装命令(批量命令:变身车辆-延迟3分钟发射5次粒子炮-变身人类-发射3发子弹)
        invoker.addCommand(new TransToCarCommand(robot)); // 变身车辆
        invoker.addCommand(new FireBulletCommand(weapon, 3)); // 发射3发子弹

        // 4. 执行所有命令(请求者负责执行,客户端无需关心细节)
        invoker.executeAll();
    }
}

// 两个指令接收者, 真正执行指令对应的功能
class Robot {
    public void transToCar() {
        System.out.println("[robot] 变身一辆车");
    }

    public void transToHuman() {
        System.out.println("[robot] 变身T800");
    }
}

class Weapon {
    public void fireBullet() {
        System.out.println("[武器] 发射子弹");
    }

    public void fireParticleCannon() {
        System.out.println("[武器] 发射粒子炮");
    }
}

执行结果:

复制代码
===== 开始执行命令队列 =====
[robot] 变身一辆车
[日志] 命令:变身车辆,执行时间:Thu Dec 18 14:21:38 CST 2025,耗时:0ms
[武器] 发射子弹
[武器] 发射子弹
[武器] 发射子弹
[日志] 命令:发射子弹,次数:3,执行时间:Thu Dec 18 14:21:38 CST 2025,耗时:0ms
===== 命令队列执行完成 =====

命令模式只解决一件事:

把"要做什么"封装成对象

其他能力(队列 / 日志 / 撤销)都是围绕这个对象自然搭建出来的

当看到代码里出现下面的逻辑:

复制代码
if (cmd == A) doA();
if (cmd == B) doB();

👉 90% 情况:该考虑命令模式了

相关推荐
雨中飘荡的记忆5 小时前
ElasticJob分布式调度从入门到实战
java·后端
_哆啦A梦13 小时前
Vibe Coding 全栈专业名词清单|设计模式·基础篇(创建型+结构型核心名词)
前端·设计模式·vibecoding
考虑考虑13 小时前
JDK25模块导入声明
java·后端·java ee
_小马快跑_15 小时前
Java 的 8 大基本数据类型:为何是不可或缺的设计?
java
Re_zero17 小时前
线上日志被清空?这段仅10行的 IO 代码里竟然藏着3个毒瘤
java·后端
洋洋技术笔记17 小时前
Spring Boot条件注解详解
java·spring boot
程序员清风1 天前
程序员兼职必看:靠谱软件外包平台挑选指南与避坑清单!
java·后端·面试
皮皮林5512 天前
利用闲置 Mac 从零部署 OpenClaw 教程 !
java
华仔啊2 天前
挖到了 1 个 Java 小特性:var,用完就回不去了
java·后端
SimonKing2 天前
SpringBoot整合秘笈:让Mybatis用上Calcite,实现统一SQL查询
java·后端·程序员