Java命令模式实战案例解析:从理论到落地的完整指南
目录导读
- 命令模式核心概念 – 什么是命令模式?解决什么问题?
- 模式结构四要素 – 命令、接收者、调用者、客户端的职责划分
- Java代码实现案例 – 智能家居遥控器(含完整源码)
- 命令模式的扩展应用 – 宏命令、撤销/重做、日志队列
- 命令模式 vs 策略模式 – 关键区别与选型建议
- 常见面试问答 – 10个高频问题精讲
- 性能与最佳实践 – 何时该用,何时慎用
命令模式核心概念
命令模式(Command Pattern) 是一种行为设计模式,它将“请求”封装为独立对象,从而让你能用不同的请求对客户端进行参数化,支持排队、日志记录以及可撤销操作。

核心痛点:当系统需要发送方(Invoker)与执行方(Receiver)完全解耦时,直接调用会导致强耦合、难以扩展,一个按钮需要绑定多种功能,且支持撤销。
生活类比:餐厅点餐 – 顾客(客户端)把订单(命令)给服务员(调用者),服务员只负责传递,厨房(接收者)知道如何做菜,这样“点餐”这个动作可以被记录、排队,甚至可以取消。
模式结构四要素
| 角色 | 职责 | 对应类 |
|---|---|---|
| Command(命令接口) | 声明执行操作的方法 execute() |
interface Command |
| ConcreteCommand(具体命令) | 绑定接收者,调用接收者的具体方法 | LightOnCommand |
| Receiver(接收者) | 真正执行业务逻辑的类 | Light |
| Invoker(调用者/请求发送者) | 持有命令对象,触发执行 | RemoteControl |
| Client(客户端) | 创建具体命令并设置接收者 | MainApp |
Java代码实现案例:智能家居遥控器
1 场景描述
实现一个智能家居遥控器,有3个按钮:开灯、关灯、打开电视,要求:
- 每个按钮可以绑定不同命令
- 支持“撤销”上一次操作
2 完整源码
// 1. 命令接口
interface Command {
void execute();
void undo();
}
// 2. 接收者:电灯
class Light {
public void on() { System.out.println("灯光已开启"); }
public void off() { System.out.println("灯光已关闭"); }
}
// 3. 具体命令:开灯命令
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) { this.light = light; }
public void execute() { light.on(); }
public void undo() { light.off(); }
}
// 4. 具体命令:关灯命令
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) { this.light = light; }
public void execute() { light.off(); }
public void undo() { light.on(); }
}
// 5. 接收者:电视
class TV {
public void powerOn() { System.out.println("电视已打开"); }
public void powerOff() { System.out.println("电视已关闭"); }
}
// 6. 具体命令:开电视
class TVOnCommand implements Command {
private TV tv;
public TVOnCommand(TV tv) { this.tv = tv; }
public void execute() { tv.powerOn(); }
public void undo() { tv.powerOff(); }
}
// 7. 调用者:遥控器(支持撤销)
class RemoteControl {
private Command[] commands = new Command[3];
private Command lastCommand;
public void setCommand(int slot, Command cmd) {
commands[slot] = cmd;
}
public void pressButton(int slot) {
if (commands[slot] != null) {
commands[slot].execute();
lastCommand = commands[slot];
}
}
public void pressUndo() {
if (lastCommand != null) {
lastCommand.undo();
}
}
}
// 8. 客户端测试
public class CommandPatternDemo {
public static void main(String[] args) {
Light livingRoomLight = new Light();
TV samsungTV = new TV();
LightOnCommand lightOn = new LightOnCommand(livingRoomLight);
LightOffCommand lightOff = new LightOffCommand(livingRoomLight);
TVOnCommand tvOn = new TVOnCommand(samsungTV);
RemoteControl remote = new RemoteControl();
remote.setCommand(0, lightOn);
remote.setCommand(1, lightOff);
remote.setCommand(2, tvOn);
// 按下按钮
remote.pressButton(0); // 输出:灯光已开启
remote.pressButton(2); // 输出:电视已打开
remote.pressUndo(); // 输出:电视已关闭(撤销)
}
}
3 执行结果
灯光已开启
电视已打开
电视已关闭
命令模式的扩展应用
1 宏命令(Composite Command)
将多个命令组合成一个宏命令,一次执行多个操作:
class MacroCommand implements Command {
private Command[] commands;
public MacroCommand(Command[] commands) { this.commands = commands; }
public void execute() {
for (Command c : commands) c.execute();
}
public void undo() {
for (int i = commands.length - 1; i >= 0; i--) commands[i].undo();
}
}
2 撤销/重做(多级撤销)
使用栈存储历史命令:
class HistoryManager {
private Stack<Command> history = new Stack<>();
public void execute(Command cmd) {
cmd.execute();
history.push(cmd);
}
public void undoLast() {
if (!history.isEmpty()) history.pop().undo();
}
}
3 日志与事务队列
将命令对象序列化到磁盘,实现系统崩溃后的恢复重放,银行转账操作可记录为命令日志。
命令模式 vs 策略模式
| 对比维度 | 命令模式 | 策略模式 |
|---|---|---|
| 目的 | 将请求封装为对象,解耦发送者与执行者 | 定义算法族,让算法可互换 |
| 关注点 | 动作的请求、撤销、队列 | 算法的封装与替换 |
| 是否含接收者 | 有独立的Receiver | 算法本身即接收者 |
| 典型应用 | 遥控器、编辑器撤销、线程池任务 | 排序算法、支付方式选择 |
选型建议:如果需要延迟执行、撤销、记录日志,用命令模式;如果只是算法替换,用策略模式更轻量。
常见面试问答(10个高频问题)
Q1:命令模式的主要优点是什么?
① 发送者与接收者完全解耦;② 支持撤销/重做;③ 支持宏命令和队列;④ 易于扩展新命令,符合开闭原则。
Q2:命令模式有哪些缺点?
类数量会增加(每增加一个命令就增加一个类),可能导致系统类膨胀。
Q3:execute() 方法中能否抛出异常?
建议不抛出,因为调用者无法预知异常处理方式,可在命令内自行捕获或转换为运行时异常。
Q4:如何实现“多级撤销”?
使用两个Stack(撤销栈和重做栈),每次执行后压入撤销栈。
Q5:命令模式在JDK中的应用?
Runnable接口就是命令模式(Thread + Runnable),javax.swing.Action也是。
Q6:命令队列的应用场景?
任务调度系统、消息队列(如订单处理流水线)、游戏中的动作回放。
Q7:客户端和调用者的区别?
客户端负责创建命令对象(setReceiver),调用者只负责持有并触发命令,不关心命令具体实现。
Q8:命令对象是否可以被序列化?
可以,但需保证其中引用的Receiver也可序列化,否则需要改为存储方法名+参数。
Q9:能否用Lambda简化命令模式?
可以!如果Command接口是函数式接口(只有一个抽象方法),可用Lambda表达式直接传方法引用,但注意Lambda无法轻松实现undo,需额外设计。
Q10:命令模式与工厂模式如何结合?
工厂模式负责创建命令对象,让客户端无需知道具体命令类名,实现“命令族”的集中管理。
性能与最佳实践
✅ 推荐使用场景
- 需要参数化请求(如按钮、菜单项)
- 需要支持撤销/重做(编辑器、绘图工具)
- 需要记录变更日志(数据库事务、文件系统快照)
- 需要事务性操作(全部成功或全部回滚)
⚠️ 注意事项
- 不要滥用:简单一对一调用不必使用命令模式,否则增加代码复杂度。
- 命令对象应轻量:避免在命令中持有大量状态,可考虑引用共享的Receiver。
- Undo/Redo设计:优先使用Stack,并注意内存消耗(大操作可限制栈深度)。
命令模式是Java开发中最优雅的解耦工具之一,尤其适合需要“可撤销”“可排队”“可日志”的业务场景,通过本文的智能家居案例,你已经掌握了命令模式的核心结构、扩展技巧以及面试要点。命令模式的核心价值在于将“行为”变成“数据”,让系统行为可以被存储、传递和回放。
你可以在自己的项目里尝试引入命令模式,从“撤销功能”或“任务队列”开始,体验它带来的灵活性和可维护性!