命令模式适合哪些应用场景?
简化版
命令模式适合请求需要被保存、排队、撤销、重做、组合或统一调度的场景。典型例子包括编辑器撤销重做、菜单按钮和快捷键、任务队列、事务补偿、宏命令、操作日志、工作流动作和远程控制器。
详细版
常见适用场景:
- UI 菜单、按钮、快捷键:不同按钮绑定不同命令;
- 撤销重做:命令保存执行历史,并提供
undo(); - 命令队列:任务封装成命令后排队执行;
- 异步任务:生产者提交命令,消费者执行命令;
- 宏命令:多个命令组合成一个批量操作;
- 事务补偿:失败后执行反向补偿命令;
- 操作日志:记录用户执行过的命令;
- 工作流系统:不同节点动作抽象成命令。
不适合的场景是简单直接调用。比如 Controller 调 Service 做普通查询,通常没必要引入命令模式。
完整版教学
一、编辑器撤销重做
文本编辑器、绘图软件、IDE 都大量使用类似命令模式的思想。
例如:
- 插入文字;
- 删除文字;
- 移动画布元素;
- 修改颜色;
- 格式化代码。
每个操作都可以封装成命令,执行后进入历史栈,撤销时调用反向操作。
二、菜单、按钮和快捷键
UI 系统里,菜单项和快捷键不应该直接绑定业务对象。
更好的方式是绑定命令:
Ctrl + S -> SaveCommand
Ctrl + Z -> UndoCommand
菜单“导出” -> ExportCommand
这样同一个命令可以被多个入口复用。按钮、菜单和快捷键都只是触发器。
三、任务队列和异步任务
后台系统常见任务:
- 发送邮件;
- 生成报表;
- 同步数据;
- 刷新缓存;
- 调用第三方接口。
这些任务可以统一实现 Command,提交到队列后由 Worker 执行。
这样任务提交方不用关心执行线程、执行时机和调度策略。
四、宏命令和批处理
宏命令把多个命令组合起来。
例如“一键发布”可能包含:
- 构建项目;
- 运行测试;
- 上传产物;
- 刷新 CDN;
- 发送通知。
调用方只执行一个 PublishCommand,内部按顺序执行多个子命令。
五、事务补偿和业务回滚
在分布式业务里,很多操作不能靠单个数据库事务解决。
命令模式可以把正向动作和补偿动作都对象化:
TryLockStockCommand
UnlockStockCommand
FreezeCouponCommand
UnfreezeCouponCommand
失败后根据执行日志触发补偿命令。
这不是完整的分布式事务方案,但能帮助组织补偿动作。
六、操作日志和审计
如果系统要记录“谁在什么时候执行了什么操作”,命令对象可以携带:
- 操作人;
- 操作时间;
- 命令类型;
- 业务参数;
- 执行结果;
- 错误信息。
这比散落在各个业务方法里记录日志更统一。
七、工作流和规则引擎
工作流系统里,节点动作可以抽象成命令:
- 审批通过;
- 审批驳回;
- 自动派单;
- 发送通知;
- 触发回调。
工作流引擎只负责调度命令,具体动作由命令实现。
八、不适合使用命令模式的情况
如果业务只是普通同步调用:
userService.getUser(id);
没有撤销、排队、日志、组合、异步等需求,就不必强行包装成 GetUserCommand。
过度设计会让代码更难读。
九、常见误区与追问
场景判断应从操作是否需要独立生命周期入手。编辑器中连续输入100个字符若逐字符建命令,会造成历史过细,通常要按时间窗口合并;而“插入图片”适合成为独立可撤销命令。异步任务、审计操作和批处理也只有在请求需要保存、调度或补偿时才真正匹配该模式。
| 检查维度 | 判定依据 |
|---|---|
| 高匹配场景 | 撤销、排队、审计、宏操作 |
| 低匹配场景 | 一次性同步调用且无附加生命周期 |
心法:看请求是否需要“离开调用现场以后继续存在”。
- 误区:所有按钮事件都应该对应一个命令类。 简单局部事件直接回调即可,只有需要统一管理时才值得对象化。
- 追问:工作流节点算命令吗? 节点若封装可执行动作和参数,可以采用命令思想,但工作流还包含状态流转规则。
- 误区:操作日志等于保存命令对象。 审计日志应使用稳定、可脱敏的数据格式,不能依赖运行时对象序列化。
- 追问:批处理为何适合宏命令? 它可以组合子命令并统一记录进度、失败点和补偿顺序。
- 追问:如何避免命令历史过细? 定义业务粒度,合并连续同类操作,并设置事务型命令边界。
十、加强记忆
命令模式适合“请求需要被管理”的场景:撤销重做、异步队列、宏命令、事务补偿、操作审计、快捷键菜单和工作流动作。普通 CRUD 没有这些需求时,直接调用更合适。