Java 23 种设计模式:从踩坑到精通 | 番外:命令模式 —— 仓储设备控制实战

📅 2026/8/3 1:49:42 👁️ 阅读次数 📝 编程学习
Java 23 种设计模式:从踩坑到精通 | 番外:命令模式 —— 仓储设备控制实战

Java 23 种设计模式:从踩坑到精通 | 番外:命令模式 —— 仓储设备控制实战

摘要:命令模式将“请求”封装为对象,从而让你可以用不同的请求对客户端参数化、对请求排队或记录日志,并天然支持撤销操作。本文结合智能仓储设备控制系统的场景,完整展示如何用命令模式实现对传送带、分拣机等设备的远程控制和撤销操作,并与策略模式深度对比,帮你掌握“将行为对象化”的设计精髓。

🗺️本文阅读地图(3 分钟速览)

  • 为什么设备控制不能直接调用设备方法?
  • ✅ 命令模式核心角色:调用者、抽象命令、具体命令、接收者
  • 手写仓储调度中心:启动/停止设备,支持多步撤销
  • 🚀 命令 vs 策略:行为封装 vs 算法替换
  • 🎤 面试必问:“命令模式和策略模式有什么区别?Runnable用了哪个?”

📖《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 正篇:命令模式 Command —— 把操作封装成对象,实现撤销与排队 |当前:番外 · 命令模式 × 仓储设备控制
🔗 返回系列总目录

1. 仓储设备控制的痛点

在智能仓库中,有传送带、分拣机、堆垛机等多种设备。操作员通过一个中央控制台来管理这些设备的启停。如果直接在控制台类中调用设备方法:

if(button.equals("start_conveyor")){conveyor1.start();}elseif(button.equals("stop_sorter")){sorter1.stop();}

这种写法将控制台与具体设备紧耦合,新增设备或修改操作逻辑都需要修改控制台代码。更复杂的是,操作员希望控制台支持撤销上一步操作一键批量操作——这用if-else几乎无法优雅实现。

命令模式的解决思路:将“启动设备”和“停止设备”这些操作封装为独立的命令对象,控制台只持有命令并触发执行,不关心命令内部是哪个设备、如何执行。所有执行过的命令记录在栈中,撤销时只需弹出栈顶并调用undo()

1.1 你的场景该不该用命令模式?

判断标准是 → 用命令模式否 → 用其他方式
需要将操作的发起者与执行者解耦
需要支持撤销/重做、命令队列、宏命令
需要在运行时动态指定、排列或执行请求
只有简单的调用,无需撤销或排队直接调用即可

2. 命令模式 UML(仓储设备控制场景)


3. 完整源码实现

3.1 抽象命令接口 (Command)

/** * 抽象命令:声明执行和撤销操作 */publicinterfaceCommand{voidexecute();voidundo();}

💬白话:所有命令都必须能“执行”和“撤销”。这就是命令模式的灵魂——命令不仅是“做什么”,还要知道“怎么反悔”。

3.2 接收者:仓库设备基类 (WarehouseDevice)

/** * 接收者:仓库设备基类 */publicabstractclassWarehouseDevice{protectedStringname;protectedbooleanisRunning;publicWarehouseDevice(Stringname){this.name=name;this.isRunning=false;}publicabstractvoidstart();publicabstractvoidstop();publicStringgetName(){returnname;}publicbooleanisRunning(){returnisRunning;}}

3.3 具体接收者:传送带 (ConveyorBelt)

/** * 具体接收者:传送带设备 */publicclassConveyorBeltextendsWarehouseDevice{publicConveyorBelt(Stringname){super(name);}@Overridepublicvoidstart(){isRunning=true;System.out.println("🏭 "+name+" 启动:传送带开始运转,速度 1.5m/s");}@Overridepublicvoidstop(){isRunning=false;System.out.println("🏭 "+name+" 停止:传送带已关闭");}}

3.4 具体接收者:分拣机 (SortingMachine)

/** * 具体接收者:分拣机设备 */publicclassSortingMachineextendsWarehouseDevice{publicSortingMachine(Stringname){super(name);}@Overridepublicvoidstart(){isRunning=true;System.out.println("🏭 "+name+" 启动:分拣机开始工作,处理能力 500件/分钟");}@Overridepublicvoidstop(){isRunning=false;System.out.println("🏭 "+name+" 停止:分拣机已关闭");}}

💬白话:这些设备就是“真正干活的”——传送带知道怎么启动和停止自己,分拣机也知道。它们不关心是谁发出的指令。

3.5 具体命令:启动/停止设备命令

/** * 具体命令:启动设备 */publicclassStartDeviceCommandimplementsCommand{privateWarehouseDevicedevice;publicStartDeviceCommand(WarehouseDevicedevice){this.device=device;}@Overridepublicvoidexecute(){device.start();}@Overridepublicvoidundo(){device.stop();}}/** * 具体命令:停止设备 */publicclassStopDeviceCommandimplementsCommand{privateWarehouseDevicedevice;publicStopDeviceCommand(WarehouseDevicedevice){this.device=device;}@Overridepublicvoidexecute(){device.stop();}@Overridepublicvoidundo(){device.start();}}

💬白话:启动命令的撤销就是停止,停止命令的撤销就是重新启动——这是最简洁的撤销实现。

3.6 调用者:设备调度中心 (DeviceScheduler)

importjava.util.Stack;/** * 调用者:设备调度中心(遥控器) */publicclassDeviceScheduler{privateStack<Command>commandHistory=newStack<>();publicvoidexecuteCommand(Commandcommand){command.execute();commandHistory.push(command);System.out.println(" ✅ 命令已执行,当前历史记录:"+commandHistory.size()+" 条\n");}publicvoidundoLastCommand(){if(commandHistory.isEmpty()){System.out.println(" ⚠️ 无命令可撤销\n");return;}Commandcommand=commandHistory.pop();command.undo();System.out.println(" ↩️ 已撤销上一个命令,剩余历史记录:"+commandHistory.size()+" 条\n");}}

💬白话:调度中心就像遥控器——它只管按下“执行”或“撤销”按钮,完全不知道控制的是传送带还是分拣机。所有执行过的命令压入栈中,撤销时弹出栈顶。

3.7 客户端测试

publicclassClient{publicstaticvoidmain(String[]args){System.out.println("=== 智能仓储设备控制系统(命令模式)===\n");ConveyorBeltconveyor1=newConveyorBelt("传送带A-1");ConveyorBeltconveyor2=newConveyorBelt("传送带B-2");SortingMachinesorter1=newSortingMachine("分拣机S-1");DeviceSchedulerscheduler=newDeviceScheduler();// 场景1~3:正常执行scheduler.executeCommand(newStartDeviceCommand(conveyor1));scheduler.executeCommand(newStartDeviceCommand(sorter1));scheduler.executeCommand(newStopDeviceCommand(conveyor1));// 场景4:撤销停止 → 传送带恢复运行scheduler.undoLastCommand();// 场景5:批量启动scheduler.executeCommand(newStartDeviceCommand(conveyor2));scheduler.executeCommand(newStartDeviceCommand(sorter1));// 场景6:紧急连续撤销scheduler.undoLastCommand();scheduler.undoLastCommand();}}

4. 运行结果

=== 智能仓储设备控制系统(命令模式)=== 🏭 传送带A-1 启动:传送带开始运转,速度 1.5m/s ✅ 命令已执行,当前历史记录:1 条 🏭 分拣机S-1 启动:分拣机开始工作,处理能力 500件/分钟 ✅ 命令已执行,当前历史记录:2 条 🏭 传送带A-1 停止:传送带已关闭 ✅ 命令已执行,当前历史记录:3 条 🏭 传送带A-1 启动:传送带开始运转,速度 1.5m/s ↩️ 已撤销上一个命令,剩余历史记录:2 条 🏭 传送带B-2 启动:传送带开始运转,速度 1.5m/s ✅ 命令已执行,当前历史记录:3 条 🏭 分拣机S-1 启动:分拣机开始工作,处理能力 500件/分钟 ✅ 命令已执行,当前历史记录:4 条 🏭 分拣机S-1 停止:分拣机已关闭 ↩️ 已撤销上一个命令,剩余历史记录:3 条 🏭 传送带B-2 停止:传送带已关闭 ↩️ 已撤销上一个命令,剩余历史记录:2 条

5. 核心角色回顾

角色职责对应代码
Command定义执行和撤销的接口Command
ConcreteCommand绑定接收者与动作StartDeviceCommand/StopDeviceCommand
Receiver真正执行具体业务逻辑ConveyorBelt/SortingMachine
Invoker持有命令,触发执行与撤销DeviceScheduler

6. 命令模式 vs 策略模式

对比项命令模式策略模式
意图封装请求为对象,支持撤销/队列封装算法族,使其可相互替换
是否支持撤销✅ 核心特性❌ 通常不需要
典型应用Runnable、仓储设备控制、事务回滚Comparator、支付方式、促销策略

💡一句话记忆:命令模式是“行为对象化”——把操作变成对象,能执行也能撤销;策略模式是“算法对象化”——把算法变成对象,能灵活替换。

7. 命令模式的优缺点

优点缺点
调用者与接收者完全解耦每个命令一个类,命令多时类数量膨胀
天然支持撤销/重做、宏命令设计复杂度增加,角色较多
符合开闭原则:新增命令无需修改现有代码命令对象持有状态时需注意线程安全

8. 六大设计原则体现

原则体现
单一职责命令只负责请求封装,设备只负责执行
开闭原则新增命令无需修改调度中心或设备
里氏替换所有命令可替换Command接口
依赖倒置调度中心依赖抽象Command
接口隔离Command只有execute()undo()
迪米特法则调度中心只与命令交互,不知设备细节

附 命令模式 UML源码(仓储设备控制场景)

@startuml title Java 23 种设计模式:从踩坑到精通 footer 折哥 | 智能物流与Java实战 ' 1. 全局样式配置 skinparam backgroundColor #FEFEFE skinparam shadowing false skinparam classBorderColor #333333 skinparam classFontColor #1A1A1A skinparam classFontSize 14 skinparam noteFontSize 12 skinparam noteFontColor #555555 skinparam arrowColor #555555 skinparam classBackgroundColor #F9F9F9 skinparam interface { BackgroundColor #E8F5E9 BorderColor #2E7D32 } ' 2. 调用者(遥控器/调度中心) class Invoker { - command : Command + setCommand(Command) + executeCommand() + undoLastCommand() } note right of Invoker <b>调用者(调度中心)</b> -- 持有命令对象,触发执行 不关心具体执行细节 支持撤销操作 end note ' 3. 抽象命令 interface Command { + execute() + undo() } note right of Command <b>抽象命令</b> -- 声明执行和撤销操作 所有具体命令都实现此接口 end note ' 4. 具体命令:开启 class LightOnCommand implements Command { - light : Light + execute() + undo() } note right of LightOnCommand <b>具体命令:开启</b> -- 持有接收者引用 执行时调用接收者的on() 撤销时调用接收者的off() end note ' 5. 具体命令:关闭 class LightOffCommand implements Command { - light : Light + execute() + undo() } note right of LightOffCommand <b>具体命令:关闭</b> -- 持有接收者引用 执行时调用接收者的off() 撤销时调用接收者的on() end note ' 6. 接收者 class Light { + on() + off() } note right of Light <b>接收者(执行者)</b> -- 真正执行具体业务逻辑 与命令解耦,无需知道谁调用 end note ' 7. 关系连线 Invoker o--> Command : 持有命令 LightOnCommand --> Light : 调用 LightOffCommand --> Light : 调用 LightOnCommand ..|> Command : 实现 LightOffCommand ..|> Command : 实现 @enduml

🧭 《Java 23 种设计模式:从踩坑到精通》快速导航

  • 开篇:系列介绍与目录
  • 正篇:命令模式 Command —— 把操作封装成对象,实现撤销与排队
  • 当前:番外 · 命令模式 × 仓储设备控制(你在这里)
  • 创建型模式汇总
  • 结构型模式汇总
  • 行为型模式汇总

🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。

📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》、《电商多平台电子面单对接实战》。技术相通,思路可鉴。