命令模式(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% 情况:该考虑命令模式了