Java 23 种设计模式:从踩坑到精通 | 命令模式 ------ 把操作封装成对象,实现撤销与排队
摘要 :当需要将请求的发起者与执行者解耦,或需要支持命令的撤销、排队、日志记录时,直接在调用处写业务逻辑会导致代码高度耦合且难以扩展。命令模式通过将请求封装为独立的对象,让请求的发送者与接收者完全解耦,从而支持参数化命令、命令队列、撤销/重做等高级功能。本文从智能家居遥控器的场景出发,完整讲解命令模式的原理、UML、代码实现、与策略模式的区别,并结合 JDK
Runnable、Spring JDBC、消息队列等框架应用,帮你掌握"将行为对象化"的设计精髓。
🗺️ 本文阅读地图(3 分钟速览)
- 为什么遥控器按钮不能写死?
- 命令模式四大角色拆解
- 手写万能遥控器(支持多步撤销 + Lambda 简化)
- 宏命令:一键执行多个操作(含异常处理)
- JDK
Runnable/ 线程池如何体现命令模式- 面试必问:命令 vs 策略,到底怎么区分?
📖 《Java 23 种设计模式:从踩坑到精通》开篇:系列介绍与目录 | 上一篇:责任链模式 | 当前:命令模式 | 下一篇:解释器模式
🔗 返回系列总目录
文章目录
- [Java 23 种设计模式:从踩坑到精通 | 命令模式 ------ 把操作封装成对象,实现撤销与排队](#Java 23 种设计模式:从踩坑到精通 | 命令模式 —— 把操作封装成对象,实现撤销与排队)
-
- [1. 从一个"万能遥控器"的需求说起](#1. 从一个“万能遥控器”的需求说起)
-
- [1.1 你的场景该不该用命令模式?](#1.1 你的场景该不该用命令模式?)
- [2. 模式定义与 UML 结构](#2. 模式定义与 UML 结构)
-
- [图文解析(配合上述 UML 图)](#图文解析(配合上述 UML 图))
- [3. 代码实现:智能家居遥控器](#3. 代码实现:智能家居遥控器)
-
- [3.1 接收者:各种家电](#3.1 接收者:各种家电)
- [3.2 抽象命令](#3.2 抽象命令)
- [3.3 具体命令(传统实现)](#3.3 具体命令(传统实现))
- [3.4 调用者:遥控器(支持多步撤销)](#3.4 调用者:遥控器(支持多步撤销))
- [3.5 客户端](#3.5 客户端)
- [3.6 Lambda 简化版(Java 8+)](#3.6 Lambda 简化版(Java 8+))
- [4. 进阶:宏命令(一键执行多个命令,含异常处理)](#4. 进阶:宏命令(一键执行多个命令,含异常处理))
- [5. 命令模式 vs 策略模式](#5. 命令模式 vs 策略模式)
- [6. 优缺点一览](#6. 优缺点一览)
- [7. 框架与实践中的应用](#7. 框架与实践中的应用)
-
- [7.1 JDK:Runnable / Callable](#7.1 JDK:Runnable / Callable)
- [7.2 Spring JDBC:JdbcTemplate](#7.2 Spring JDBC:JdbcTemplate)
- [7.3 消息队列 / 作业调度](#7.3 消息队列 / 作业调度)
- [8. 面试必问 + 面试官追问连环炮](#8. 面试必问 + 面试官追问连环炮)
- [9. 六大设计原则在命令模式中的体现](#9. 六大设计原则在命令模式中的体现)
- [10 实战案例:电子面单多平台对接](#10 实战案例:电子面单多平台对接)
- [《Java 23 种设计模式:从踩坑到精通》快速导航](#《Java 23 种设计模式:从踩坑到精通》快速导航)
1. 从一个"万能遥控器"的需求说起
假设你在开发一款智能家居遥控器,它需要控制电灯、空调、电视等多种设备,每个设备有开和关两个操作。如果直接在遥控器类中写死:
java
if (button.equals("light_on")) {
light.on();
} else if (button.equals("ac_on")) {
ac.on();
}
// ...
遥控器与具体设备高度耦合,新增设备或操作都需要修改遥控器代码。更复杂的是,用户希望遥控器支持撤销上一次操作 和一键执行多个命令 ,这用 if-else 几乎无法优雅实现。
命令模式(Command Pattern)正是为解决这类问题而生的:它将"请求"封装成对象,从而让你可以用不同的请求对客户端参数化、对请求排队或记录请求日志,以及支持可撤销的操作。
1.1 你的场景该不该用命令模式?
| 判断标准 | 是 → 用命令模式 | 否 → 用其他方式 |
|---|---|---|
| 需要将请求的发起者与执行者解耦 | ✅ | ❌ |
| 需要支持撤销/重做、命令队列、宏命令 | ✅ | ❌ |
| 需要在运行时动态指定、排列或执行请求 | ✅ | ❌ |
| 只有简单的调用,无需撤销或排队 | ❌ | 直接调用即可 |
2. 模式定义与 UML 结构
命令模式 将一个请求封装为一个对象,从而使你可以用不同的请求对客户端进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。它属于 行为型设计模式。

图文解析(配合上述 UML 图)
命令模式的核心角色如下:
- 抽象命令(
Command) :定义统一的execute()和undo()接口,让所有命令都可被调用和撤销。 - 具体命令(
LightOnCommand/LightOffCommand) :将接收者 与一个动作绑定,实现execute()去调用接收者的方法,undo()执行反向操作。 - 接收者(
Light):知道如何实施与执行一个请求相关的操作,是真正干活的对象。 - 调用者(
Invoker) :持有命令对象,在某个时间点调用命令的execute()。它不需要知道命令是如何实现的,只需按下按钮。
核心机制 :命令模式把"调用"这个行为对象化,让 Invoker 和 Receiver 之间没有直接依赖,只有命令对象作为中间人。
3. 代码实现:智能家居遥控器
3.1 接收者:各种家电
java
// 电灯
public class Light {
public void on() { System.out.println("电灯打开"); }
public void off() { System.out.println("电灯关闭"); }
}
// 空调
public class AirConditioner {
public void on() { System.out.println("空调打开,制冷模式 26°C"); }
public void off() { System.out.println("空调关闭"); }
}
// 电视
public class TV {
public void on() { System.out.println("电视打开"); }
public void off() { System.out.println("电视关闭"); }
}
💬 白话:这些就是真正被控制的设备,每个设备都有自己的开关逻辑。
3.2 抽象命令
java
public interface Command {
void execute();
void undo();
}
💬 白话:所有命令都必须有"执行"和"撤销"两种能力。
3.3 具体命令(传统实现)
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(); } // 反向操作
}
// 关灯命令
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(); }
}
// 开空调命令
public class ACOnCommand implements Command {
private AirConditioner ac;
public ACOnCommand(AirConditioner ac) { this.ac = ac; }
@Override
public void execute() { ac.on(); }
@Override
public void undo() { ac.off(); }
}
// 关空调命令
public class ACOffCommand implements Command {
private AirConditioner ac;
public ACOffCommand(AirConditioner ac) { this.ac = ac; }
@Override
public void execute() { ac.off(); }
@Override
public void undo() { ac.on(); }
}
💬 白话 :每个命令都知道自己操作哪个设备,并定义好反向操作,这样撤销时直接调用
undo()即可。
3.4 调用者:遥控器(支持多步撤销)
使用 Stack<Command> 替代单个变量,实现连续撤销。
java
public class RemoteControl {
private Command[] onCommands;
private Command[] offCommands;
private Stack<Command> undoStack = new Stack<>(); // 支持多步撤销
private static final int MAX_UNDO = 20; // 限制最大撤销步数
public RemoteControl(int slotCount) {
onCommands = new Command[slotCount];
offCommands = new Command[slotCount];
Command noCommand = new NoCommand();
for (int i = 0; i < slotCount; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void pressOnButton(int slot) {
onCommands[slot].execute();
pushUndo(onCommands[slot]);
}
public void pressOffButton(int slot) {
offCommands[slot].execute();
pushUndo(offCommands[slot]);
}
public void pressUndoButton() {
if (!undoStack.isEmpty()) {
Command lastCommand = undoStack.pop();
lastCommand.undo();
} else {
System.out.println("没有可撤销的操作");
}
}
private void pushUndo(Command command) {
undoStack.push(command);
// 限制栈大小,避免内存溢出
if (undoStack.size() > MAX_UNDO) {
undoStack.remove(0);
}
}
}
// 空命令(避免 null 判断)
class NoCommand implements Command {
@Override public void execute() {}
@Override public void undo() {}
}
💬 白话 :遥控器把执行过的命令存入栈中,撤销时弹出最近一条命令并调用其
undo(),实现多步撤销。
3.5 客户端
java
Light livingRoomLight = new Light();
AirConditioner ac = new AirConditioner();
LightOnCommand lightOn = new LightOnCommand(livingRoomLight);
LightOffCommand lightOff = new LightOffCommand(livingRoomLight);
ACOnCommand acOn = new ACOnCommand(ac);
ACOffCommand acOff = new ACOffCommand(ac);
RemoteControl remote = new RemoteControl(2);
remote.setCommand(0, lightOn, lightOff);
remote.setCommand(1, acOn, acOff);
remote.pressOnButton(0); // 电灯打开
remote.pressOnButton(1); // 空调打开
remote.pressUndoButton(); // 空调关闭
remote.pressUndoButton(); // 电灯关闭
3.6 Lambda 简化版(Java 8+)
对于逻辑简单的命令,不必为每一个操作新建类,可以用函数式接口直接创建命令。
java
public class FunctionalRemoteControl {
private Command[] onCommands;
private Command[] offCommands;
private Stack<Command> undoStack = new Stack<>();
public FunctionalRemoteControl(int slotCount) {
onCommands = new Command[slotCount];
offCommands = new Command[slotCount];
Command noCommand = () -> {};
Arrays.fill(onCommands, noCommand);
Arrays.fill(offCommands, noCommand);
}
// 直接传入 Runnable 作为执行逻辑
public void setOnCommand(int slot, Runnable action, Runnable undoAction) {
onCommands[slot] = new Command() {
@Override
public void execute() { action.run(); }
@Override
public void undo() { undoAction.run(); }
};
}
// ... 其余方法类似
}
💬 白话:简单操作不再需要单独的类文件,直接用 Lambda 表达式描述"做什么"和"如何撤销"。
4. 进阶:宏命令(一键执行多个命令,含异常处理)
java
public class MacroCommand implements Command {
private List<Command> commands;
public MacroCommand(List<Command> commands) { this.commands = commands; }
@Override
public void execute() {
for (Command cmd : commands) {
try {
cmd.execute();
} catch (Exception e) {
// 简单处理:记录失败并继续执行后续命令
System.err.println("命令执行失败: " + e.getMessage());
// 若需原子性,可在此实现预检查或补偿逻辑
}
}
}
@Override
public void undo() {
// 逆序撤销,遇到失败同样记录并继续
for (int i = commands.size() - 1; i >= 0; i--) {
try {
commands.get(i).undo();
} catch (Exception e) {
System.err.println("撤销失败: " + e.getMessage());
}
}
}
}
💬 白话:宏命令打包多个命令,一键执行,撤销时倒序撤销。增加了异常处理,避免中间失败导致流程中断。
客户端使用:
java
List<Command> partyCommands = Arrays.asList(lightOn, acOn);
MacroCommand partyMode = new MacroCommand(partyCommands);
remote.setCommand(2, partyMode, new NoCommand());
remote.pressOnButton(2); // 电灯打开 → 空调打开
remote.pressUndoButton(); // 空调关闭 → 电灯关闭
5. 命令模式 vs 策略模式
| 对比维度 | 命令模式 | 策略模式 |
|---|---|---|
| 目的 | 将请求封装为对象,解耦调用者与执行者 | 封装一系列算法,使其可相互替换 |
| 侧重点 | 请求的封装与传递,支持撤销/队列 | 算法的封装与替换 |
| 典型应用 | 遥控器、作业队列、撤销操作 | 支付方式、排序算法、折扣策略 |
| 是否关注撤销 | 是 | 否 |
💡 简单记忆:命令模式是"行为对象化",策略模式是"算法对象化"。策略模式通常需要一个上下文(Context)持有当前策略并调用算法,而命令模式则由调用者发起动作,更强调"动作"本身。
6. 优缺点一览
| 优点 | 缺点 |
|---|---|
| 解耦:调用者与接收者完全解耦 | 类膨胀:每个命令都是一个类,命令多时类数量大增 |
| 可扩展:新增命令无需修改现有代码,符合开闭原则 | 理解成本:角色较多,初学时不易理解 |
| 支持撤销/重做:通过状态栈实现多步撤销 | 命令对象若持有可变状态,在多线程下需要额外同步 |
| 支持组合:宏命令可将多个命令组合执行 |
⚠️ 线程安全提醒 :如果命令对象本身持有可变状态(如记录操作前数据),在多线程环境下需保证其不可变或使用同步机制。推荐将命令设计为无状态,状态由外部传入或由接收者管理,这样可以安全地在线程池中执行。
7. 框架与实践中的应用
7.1 JDK:Runnable / Callable
Runnable 接口就是典型的命令模式。线程池中的线程是 Invoker,Runnable 是 Command,具体业务逻辑在 run() 中实现。
java
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.execute(() -> System.out.println("执行任务")); // 命令被线程池调度执行
7.2 Spring JDBC:JdbcTemplate
JdbcTemplate 使用命令模式将 SQL 操作封装为 PreparedStatementCallback、ResultSetExtractor 等回调对象,由 JdbcTemplate 统一调用。
7.3 消息队列 / 作业调度
消息队列中的消息本质上就是序列化的命令对象。消费者接收到消息后执行 execute(),支持异步处理、重试、顺序执行。
8. 面试必问 + 面试官追问连环炮
基础必问
- 命令模式的四个角色? → Command、ConcreteCommand、Invoker、Receiver。
Runnable是什么模式? → 命令模式。- 如何实现撤销? → 在命令对象中存储操作前的状态,或执行反向操作,配合栈实现多步撤销。
- 命令模式和策略模式的区别? → 命令关注请求的封装与调用者解耦,策略关注算法的封装与替换。
面试官追问
- "宏命令的撤销为什么是逆序?"
👉 因为后执行的操作依赖先执行的结果,逆序撤销才能恢复正确状态。 - "命令模式怎么实现重试?"
👉 把命令对象放入队列,执行失败时重新入队,或通过execute()内部循环重试。 - "命令模式和观察者模式能配合吗?"
👉 可以。触发命令执行后,通过观察者通知 UI 更新状态(如按钮颜色变化)。
🎉 恭喜 :如果你能立刻说出
Runnable是命令模式,并理解宏命令的逆序撤销机制,你已经掌握了行为型模式中最灵活的"请求封装"设计。
9. 六大设计原则在命令模式中的体现
| 设计原则 | 在命令模式中的体现 |
|---|---|
| 单一职责原则(SRP) | 命令类只负责封装请求,接收者只负责执行 |
| 开闭原则(OCP) | 新增命令无需修改 Invoker 或 Receiver |
| 里氏替换原则(LSP) | 所有命令都遵循 Command 接口,可相互替换 |
| 依赖倒置原则(DIP) | Invoker 依赖抽象 Command,不依赖具体命令 |
| 接口隔离原则(ISP) | Command 接口只定义 execute() 和 undo(),精简 |
| 迪米特法则(LoD) | 客户端只需配置命令,无需知道 Receiver 的细节 |
💡 进阶思考:命令模式是事件溯源(Event Sourcing)和 AI Agent 工具调用的基础。将命令对象持久化,你就能重现系统的任何历史状态;在 AI 应用中,模型输出的"动作指令"可被转换为命令对象执行,实现可插拔的工具集成。
10 实战案例:电子面单多平台对接
本文讲解的设计模式,已在真实电商项目中落地。如果你想看这些模式在十余个平台、日均10w+订单场景下的完整应用,欢迎阅读我的电子面单实战系列:
📖 电商多平台电子面单对接实战
- 系列开篇:从"能跑就行"到"整洁架构"
- 多平台统一架构设计 ------ 策略模式+模板方法+工厂模式的完整落地
- 策略工厂复合Key路由改造 ------ 工厂模式的演进实战
- 更多文章持续更新中...
💡 学习建议:先掌握本文的设计模式理论,再到电子面单系列中看它们如何被实际应用,理论与实践结合,面试和项目都能用上。
《Java 23 种设计模式:从踩坑到精通》快速导航
- 开篇:系列介绍与目录
- 上一篇:责任链模式 ------ 请求流转,审批流程的本质
- 当前:命令模式 ------ 把操作封装成对象,实现撤销与排队(你在这里)
- [下一篇:解释器模式 ------ 自己动手写一个小语言解释器](#下一篇:解释器模式 —— 自己动手写一个小语言解释器) 🚧 即将发布
- 创建型模式汇总:单例、工厂、建造者、原型
- 结构型模式汇总:适配器、装饰器、代理......
- 行为型模式汇总:观察者、策略、模板方法......
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告 :全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀 下一篇:解释器模式:自己动手写一个小语言解释器!🚧 即将发布,敬请关注!
📌 除了设计模式,我也在深挖智能物流实战 (WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。