命令模式的优缺点是什么?
简化版
命令模式的优点是解耦调用者和接收者,让请求可以排队、记录、撤销、重做和组合;缺点是会增加命令类数量,简单场景容易过度设计,并且撤销、补偿、失败处理如果设计不好会让系统复杂度升高。
详细版
命令模式的主要优点:
- 调用者和执行者解耦;
- 新增命令符合开闭原则;
- 支持撤销和重做;
- 支持命令队列和异步执行;
- 支持宏命令和批处理;
- 便于记录操作日志和审计。
主要缺点:
- 每个操作可能都要一个命令类,类数量增多;
- 简单调用会变复杂;
- 撤销和补偿并不总是容易实现;
- 命令对象可能持有过多上下文,导致职责膨胀;
- 异步命令需要额外处理幂等、重试和失败状态。
所以命令模式适合“请求需要被管理”的场景,不适合所有方法调用都套一层。
完整版教学
一、优点:解耦调用者和执行者
没有命令模式时,调用者直接依赖执行者:
Button -> Light
用了命令模式:
Button -> Command -> Light
Button 只依赖 Command 抽象,不依赖具体设备。
以后按钮绑定风扇命令、音响命令、空调命令,都不用修改按钮类。
二、优点:请求可以被保存和传递
命令对象是普通对象,因此可以放进集合、队列、栈和日志系统。
这让系统可以实现:
- 延迟执行;
- 异步执行;
- 任务排队;
- 操作历史;
- 撤销重做;
- 批量执行。
这也是命令模式区别于普通回调或直接调用的关键价值。
三、优点:扩展新命令比较自然
新增命令通常只需要增加一个类:
class OpenDoorCommand implements Command {
public void execute() {
door.open();
}
}
Invoker 不需要修改,因为它只依赖 Command。
这符合开闭原则:对扩展开放,对修改关闭。
四、缺点:命令类可能变多
如果系统有很多操作,每个操作都写一个命令类,类数量会快速膨胀。
例如:
CreateOrderCommand
CancelOrderCommand
PayOrderCommand
RefundOrderCommand
ShipOrderCommand
类多并不一定是坏事,但如果这些命令只是简单转发 service 方法,收益就不明显。
五、缺点:撤销和补偿很容易被低估
本地内存对象的撤销比较简单,但真实业务不一定可逆。
例如:
- 钱已经转出;
- 短信已经发送;
- 第三方接口已经调用;
- 库存状态已被其他请求改变。
这种情况下,undo() 不是简单反向调用,而是一个完整补偿流程。
六、缺点:异步命令会引入可靠性问题
如果命令进入异步队列,还要考虑:
- 命令参数如何序列化;
- 任务是否会重复执行;
- 消费失败如何重试;
- 重试是否幂等;
- 执行状态如何追踪;
- 失败是否需要告警。
命令模式只提供结构,不自动解决分布式可靠性。
七、什么时候优点大于缺点
当系统存在以下需求时,命令模式通常值得用:
- 操作要撤销;
- 操作要排队;
- 操作要异步执行;
- 操作要写日志;
- 操作要组合;
- 调用者和执行者需要明显解耦。
如果只是普通同步调用,直接调用 service 方法更清爽。
八、常见误区与追问
命令模式的收益来自“请求对象可被操作”,代价则是类型和状态管理增加。若一个系统有20种可排队操作,统一命令接口可让调度器保持稳定;但20个仅转发的类也会增加导航与测试成本。评估时要把撤销、持久化、审计和异步调度的真实需求,与新增对象数量一起计算。
| 检查维度 | 判定依据 |
|---|---|
| 核心收益 | 请求可存储、组合、排队和撤销 |
| 主要代价 | 类型增多,状态与失败处理更复杂 |
心法:命令模式不是为了少写代码,而是为了让请求拥有生命周期。
- 误区:命令模式一定减少类数量。 它通常会增加具体命令类,换取调用者稳定和统一调度能力。
- 追问:什么时候收益最明显? 同一请求需要延迟执行、重放、审计或撤销时,对象化价值最大。
- 误区:解耦后 Receiver 可以随意改变接口。 具体命令仍依赖 Receiver,接口变化会影响相应命令实现。
- 追问:命令历史会带来什么成本? 需要限制容量、处理敏感参数,并定义快照过期和清理策略。
- 追问:如何量化是否值得使用? 比较新增命令类型和测试成本,与减少的分派分支及获得的调度能力。
九、加强记忆
命令模式的优点来自“请求对象化”:能解耦、能排队、能记录、能撤销、能组合。缺点也来自这件事:类会变多,简单调用会变重,撤销和异步可靠性要额外设计。是否使用它,要看请求有没有被管理的需求。