Java 23 种设计模式从踩坑到精通 | 命令模式 —— 把操作封装成对象实现撤销与排队摘要当需要将请求的发起者与执行者解耦或需要支持命令的撤销、排队、日志记录时直接在调用处写业务逻辑会导致代码高度耦合且难以扩展。命令模式通过将请求封装为独立的对象让请求的发送者与接收者完全解耦从而支持参数化命令、命令队列、撤销/重做等高级功能。本文从智能家居遥控器的场景出发完整讲解命令模式的原理、UML、代码实现、与策略模式的区别并结合 JDKRunnable、Spring JDBC、消息队列等框架应用帮你掌握“将行为对象化”的设计精髓。️本文阅读地图3 分钟速览为什么遥控器按钮不能写死命令模式四大角色拆解手写万能遥控器支持多步撤销 Lambda 简化宏命令一键执行多个操作含异常处理JDKRunnable/ 线程池如何体现命令模式面试必问命令 vs 策略到底怎么区分《Java 23 种设计模式从踩坑到精通》开篇系列介绍与目录 上一篇责任链模式 当前命令模式 下一篇解释器模式 返回系列总目录文章目录Java 23 种设计模式从踩坑到精通 | 命令模式 —— 把操作封装成对象实现撤销与排队1. 从一个“万能遥控器”的需求说起1.1 你的场景该不该用命令模式2. 模式定义与 UML 结构图文解析配合上述 UML 图3. 代码实现智能家居遥控器3.1 接收者各种家电3.2 抽象命令3.3 具体命令传统实现3.4 调用者遥控器支持多步撤销3.5 客户端3.6 Lambda 简化版Java 84. 进阶宏命令一键执行多个命令含异常处理5. 命令模式 vs 策略模式6. 优缺点一览7. 框架与实践中的应用7.1 JDKRunnable / Callable7.2 Spring JDBCJdbcTemplate7.3 消息队列 / 作业调度8. 面试必问 面试官追问连环炮9. 六大设计原则在命令模式中的体现10 实战案例电子面单多平台对接《Java 23 种设计模式从踩坑到精通》快速导航1. 从一个“万能遥控器”的需求说起假设你在开发一款智能家居遥控器它需要控制电灯、空调、电视等多种设备每个设备有开和关两个操作。如果直接在遥控器类中写死if(button.equals(light_on)){light.on();}elseif(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 接收者各种家电// 电灯publicclassLight{publicvoidon(){System.out.println(电灯打开);}publicvoidoff(){System.out.println(电灯关闭);}}// 空调publicclassAirConditioner{publicvoidon(){System.out.println(空调打开制冷模式 26°C);}publicvoidoff(){System.out.println(空调关闭);}}// 电视publicclassTV{publicvoidon(){System.out.println(电视打开);}publicvoidoff(){System.out.println(电视关闭);}}白话这些就是真正被控制的设备每个设备都有自己的开关逻辑。3.2 抽象命令publicinterfaceCommand{voidexecute();voidundo();}白话所有命令都必须有“执行”和“撤销”两种能力。3.3 具体命令传统实现// 开灯命令publicclassLightOnCommandimplementsCommand{privateLightlight;publicLightOnCommand(Lightlight){this.lightlight;}Overridepublicvoidexecute(){light.on();}Overridepublicvoidundo(){light.off();}// 反向操作}// 关灯命令publicclassLightOffCommandimplementsCommand{privateLightlight;publicLightOffCommand(Lightlight){this.lightlight;}Overridepublicvoidexecute(){light.off();}Overridepublicvoidundo(){light.on();}}// 开空调命令publicclassACOnCommandimplementsCommand{privateAirConditionerac;publicACOnCommand(AirConditionerac){this.acac;}Overridepublicvoidexecute(){ac.on();}Overridepublicvoidundo(){ac.off();}}// 关空调命令publicclassACOffCommandimplementsCommand{privateAirConditionerac;publicACOffCommand(AirConditionerac){this.acac;}Overridepublicvoidexecute(){ac.off();}Overridepublicvoidundo(){ac.on();}}白话每个命令都知道自己操作哪个设备并定义好反向操作这样撤销时直接调用undo()即可。3.4 调用者遥控器支持多步撤销使用StackCommand替代单个变量实现连续撤销。publicclassRemoteControl{privateCommand[]onCommands;privateCommand[]offCommands;privateStackCommandundoStacknewStack();// 支持多步撤销privatestaticfinalintMAX_UNDO20;// 限制最大撤销步数publicRemoteControl(intslotCount){onCommandsnewCommand[slotCount];offCommandsnewCommand[slotCount];CommandnoCommandnewNoCommand();for(inti0;islotCount;i){onCommands[i]noCommand;offCommands[i]noCommand;}}publicvoidsetCommand(intslot,CommandonCommand,CommandoffCommand){onCommands[slot]onCommand;offCommands[slot]offCommand;}publicvoidpressOnButton(intslot){onCommands[slot].execute();pushUndo(onCommands[slot]);}publicvoidpressOffButton(intslot){offCommands[slot].execute();pushUndo(offCommands[slot]);}publicvoidpressUndoButton(){if(!undoStack.isEmpty()){CommandlastCommandundoStack.pop();lastCommand.undo();}else{System.out.println(没有可撤销的操作);}}privatevoidpushUndo(Commandcommand){undoStack.push(command);// 限制栈大小避免内存溢出if(undoStack.size()MAX_UNDO){undoStack.remove(0);}}}// 空命令避免 null 判断classNoCommandimplementsCommand{Overridepublicvoidexecute(){}Overridepublicvoidundo(){}}白话遥控器把执行过的命令存入栈中撤销时弹出最近一条命令并调用其undo()实现多步撤销。3.5 客户端LightlivingRoomLightnewLight();AirConditioneracnewAirConditioner();LightOnCommandlightOnnewLightOnCommand(livingRoomLight);LightOffCommandlightOffnewLightOffCommand(livingRoomLight);ACOnCommandacOnnewACOnCommand(ac);ACOffCommandacOffnewACOffCommand(ac);RemoteControlremotenewRemoteControl(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对于逻辑简单的命令不必为每一个操作新建类可以用函数式接口直接创建命令。publicclassFunctionalRemoteControl{privateCommand[]onCommands;privateCommand[]offCommands;privateStackCommandundoStacknewStack();publicFunctionalRemoteControl(intslotCount){onCommandsnewCommand[slotCount];offCommandsnewCommand[slotCount];CommandnoCommand()-{};Arrays.fill(onCommands,noCommand);Arrays.fill(offCommands,noCommand);}// 直接传入 Runnable 作为执行逻辑publicvoidsetOnCommand(intslot,Runnableaction,RunnableundoAction){onCommands[slot]newCommand(){Overridepublicvoidexecute(){action.run();}Overridepublicvoidundo(){undoAction.run();}};}// ... 其余方法类似}白话简单操作不再需要单独的类文件直接用 Lambda 表达式描述“做什么”和“如何撤销”。4. 进阶宏命令一键执行多个命令含异常处理publicclassMacroCommandimplementsCommand{privateListCommandcommands;publicMacroCommand(ListCommandcommands){this.commandscommands;}Overridepublicvoidexecute(){for(Commandcmd:commands){try{cmd.execute();}catch(Exceptione){// 简单处理记录失败并继续执行后续命令System.err.println(命令执行失败: e.getMessage());// 若需原子性可在此实现预检查或补偿逻辑}}}Overridepublicvoidundo(){// 逆序撤销遇到失败同样记录并继续for(inticommands.size()-1;i0;i--){try{commands.get(i).undo();}catch(Exceptione){System.err.println(撤销失败: e.getMessage());}}}}白话宏命令打包多个命令一键执行撤销时倒序撤销。增加了异常处理避免中间失败导致流程中断。客户端使用ListCommandpartyCommandsArrays.asList(lightOn,acOn);MacroCommandpartyModenewMacroCommand(partyCommands);remote.setCommand(2,partyMode,newNoCommand());remote.pressOnButton(2);// 电灯打开 → 空调打开remote.pressUndoButton();// 空调关闭 → 电灯关闭5. 命令模式 vs 策略模式对比维度命令模式策略模式目的将请求封装为对象解耦调用者与执行者封装一系列算法使其可相互替换侧重点请求的封装与传递支持撤销/队列算法的封装与替换典型应用遥控器、作业队列、撤销操作支付方式、排序算法、折扣策略是否关注撤销是否简单记忆命令模式是“行为对象化”策略模式是“算法对象化”。策略模式通常需要一个上下文Context持有当前策略并调用算法而命令模式则由调用者发起动作更强调“动作”本身。6. 优缺点一览优点缺点解耦调用者与接收者完全解耦类膨胀每个命令都是一个类命令多时类数量大增可扩展新增命令无需修改现有代码符合开闭原则理解成本角色较多初学时不易理解支持撤销/重做通过状态栈实现多步撤销命令对象若持有可变状态在多线程下需要额外同步支持组合宏命令可将多个命令组合执行⚠️线程安全提醒如果命令对象本身持有可变状态如记录操作前数据在多线程环境下需保证其不可变或使用同步机制。推荐将命令设计为无状态状态由外部传入或由接收者管理这样可以安全地在线程池中执行。7. 框架与实践中的应用7.1 JDKRunnable / CallableRunnable接口就是典型的命令模式。线程池中的线程是InvokerRunnable是Command具体业务逻辑在run()中实现。ExecutorServicepoolExecutors.newFixedThreadPool(10);pool.execute(()-System.out.println(执行任务));// 命令被线程池调度执行7.2 Spring JDBCJdbcTemplateJdbcTemplate使用命令模式将 SQL 操作封装为PreparedStatementCallback、ResultSetExtractor等回调对象由JdbcTemplate统一调用。7.3 消息队列 / 作业调度消息队列中的消息本质上就是序列化的命令对象。消费者接收到消息后执行execute()支持异步处理、重试、顺序执行。8. 面试必问 面试官追问连环炮基础必问命令模式的四个角色→ Command、ConcreteCommand、Invoker、Receiver。Runnable是什么模式→ 命令模式。如何实现撤销→ 在命令对象中存储操作前的状态或执行反向操作配合栈实现多步撤销。命令模式和策略模式的区别→ 命令关注请求的封装与调用者解耦策略关注算法的封装与替换。面试官追问“宏命令的撤销为什么是逆序” 因为后执行的操作依赖先执行的结果逆序撤销才能恢复正确状态。“命令模式怎么实现重试” 把命令对象放入队列执行失败时重新入队或通过execute()内部循环重试。“命令模式和观察者模式能配合吗” 可以。触发命令执行后通过观察者通知 UI 更新状态如按钮颜色变化。恭喜如果你能立刻说出Runnable是命令模式并理解宏命令的逆序撤销机制你已经掌握了行为型模式中最灵活的“请求封装”设计。9. 六大设计原则在命令模式中的体现设计原则在命令模式中的体现单一职责原则SRP命令类只负责封装请求接收者只负责执行开闭原则OCP新增命令无需修改 Invoker 或 Receiver里氏替换原则LSP所有命令都遵循Command接口可相互替换依赖倒置原则DIPInvoker 依赖抽象Command不依赖具体命令接口隔离原则ISPCommand接口只定义execute()和undo()精简迪米特法则LoD客户端只需配置命令无需知道 Receiver 的细节进阶思考命令模式是事件溯源Event Sourcing和 AI Agent 工具调用的基础。将命令对象持久化你就能重现系统的任何历史状态在 AI 应用中模型输出的“动作指令”可被转换为命令对象执行实现可插拔的工具集成。10 实战案例电子面单多平台对接本文讲解的设计模式已在真实电商项目中落地。如果你想看这些模式在十余个平台、日均10w订单场景下的完整应用欢迎阅读我的电子面单实战系列电商多平台电子面单对接实战系列开篇从“能跑就行”到“整洁架构”多平台统一架构设计 —— 策略模式模板方法工厂模式的完整落地策略工厂复合Key路由改造 —— 工厂模式的演进实战更多文章持续更新中…学习建议先掌握本文的设计模式理论再到电子面单系列中看它们如何被实际应用理论与实践结合面试和项目都能用上。《Java 23 种设计模式从踩坑到精通》快速导航开篇系列介绍与目录上一篇责任链模式 —— 请求流转审批流程的本质当前命令模式 —— 把操作封装成对象实现撤销与排队你在这里下一篇解释器模式 —— 自己动手写一个小语言解释器 即将发布创建型模式汇总单例、工厂、建造者、原型结构型模式汇总适配器、装饰器、代理……行为型模式汇总观察者、策略、模板方法…… 关注《Java 23 种设计模式从踩坑到精通》用 25 篇文章彻底吃透设计模式。福利预告全系列代码及 UML 源码将在完结时统一打包开放点击「关注」「收藏」第一时间获取。下一篇解释器模式自己动手写一个小语言解释器 即将发布敬请关注 除了设计模式我也在深挖智能物流实战WMS、托盘调度、机器学习落地。欢迎点击头像看看专栏 《出版社物流WMS智能调度实战》。技术相通思路可鉴。