命令模式中的宏命令和事务命令怎么理解?
简化版
宏命令是把多个命令组合成一个更大的命令,一次执行多个操作;事务命令关注一组操作的成功、失败和回滚或补偿。两者都利用了命令对象可组合、可记录、可统一调度的特点。
详细版
宏命令常见形式:
MacroCommand
├── Command A
├── Command B
└── Command C
执行宏命令时,按顺序执行子命令。
如果需要撤销,可以反向调用已成功命令的 undo():
execute: A -> B -> C
undo: C -> B -> A
事务命令比宏命令更关注一致性。执行过程中如果某一步失败,需要决定是整体回滚、补偿已完成步骤,还是记录失败等待人工处理。
在数据库本地事务里可以依赖事务回滚;在分布式业务里通常要设计补偿命令,类似 Saga 的思想。
完整版教学
一、宏命令是什么
宏命令就是一个命令里包含多个子命令。
class MacroCommand implements Command {
private final List<Command> commands = new ArrayList<>();
void add(Command command) {
commands.add(command);
}
public void execute() {
for (Command command : commands) {
command.execute();
}
}
public void undo() {
for (int i = commands.size() - 1; i >= 0; i--) {
commands.get(i).undo();
}
}
}
它把一组操作当成一个整体。
例如“一键下班”宏命令:
- 关灯;
- 关空调;
- 锁门;
- 打开安防。
用户只点一次按钮,系统执行多个命令。
二、为什么撤销要反向执行
假设执行顺序是:
A 创建目录
B 创建文件
C 写入内容
撤销时必须反过来:
C 删除内容
B 删除文件
A 删除目录
如果先撤销 A,目录里还有文件,可能删除失败。
所以宏命令撤销通常遵循后进先出。
三、事务命令关注失败后的状态一致性
宏命令只说明多个命令组合执行,但没有天然保证全部成功。
如果执行到第三步失败:
A 成功
B 成功
C 失败
系统必须决定怎么处理 A 和 B。
常见策略:
- 如果在同一个数据库事务中,直接回滚;
- 如果是跨系统操作,执行补偿命令;
- 如果无法自动补偿,记录异常并人工介入;
- 如果允许部分成功,明确记录最终状态。
四、本地事务命令示例
如果多个操作都在同一个数据库里,可以用事务包裹:
class CreateOrderCommand implements Command {
public void execute() {
transactionTemplate.execute(status -> {
orderService.create();
stockService.lock();
couponService.freeze();
return null;
});
}
}
这种场景里,数据库事务负责原子性,命令对象负责封装请求。
五、分布式场景更接近补偿事务
如果命令涉及多个系统:
订单服务创建订单
库存服务锁库存
优惠券服务冻结优惠券
支付服务创建支付单
它们无法靠一个本地数据库事务保证一致性。
这时可以为每个动作设计补偿命令:
锁库存 -> 解锁库存
冻结优惠券 -> 解冻优惠券
创建支付单 -> 关闭支付单
执行失败时,按相反顺序执行已完成动作的补偿命令。
六、事务命令要注意幂等和可观测性
补偿命令可能重复执行,也可能执行失败。
工程里要补充:
- 命令唯一 ID;
- 子命令执行状态;
- 重试次数;
- 补偿日志;
- 幂等校验;
- 告警和人工处理入口。
命令模式可以帮助组织这些动作,但不能替代分布式事务方案。
七、常见误区与追问
宏命令强调组合执行,事务命令强调失败后的原子性或补偿,两者不能直接画等号。设宏命令依次执行 A、B、C,若 C 失败,撤销顺序应为 B、A,而不是 A、B;已经产生的外部通知还可能无法真正回滚。数据库事务可以提供本地 ACID,跨服务场景通常只能设计可重试的补偿动作。
| 检查维度 | 判定依据 |
|---|---|
| 宏命令 | 组合多个命令,关注批量与顺序 |
| 事务命令 | 关注失败边界、回滚或补偿 |
记忆钩子:正向 A→B→C,补偿要 C→B→A,并且每一步都要允许重入。
- 误区:宏命令天然具有事务性。 组合只定义执行顺序,不会自动提供隔离、原子提交或失败回滚。
- 追问:为什么补偿要逆序执行? 后执行步骤通常依赖前一步结果,逆序更符合依赖栈的拆除顺序。
- 误区:补偿动作一定能恢复原状。 邮件发送、第三方扣款等副作用可能只能追加冲正记录,不能抹除历史。
- 追问:部分补偿失败怎么办? 保存步骤状态并允许幂等重试,超过阈值后转人工处理。
- 追问:何时直接用数据库事务? 所有步骤位于同一数据库事务边界且耗时可控时,本地事务通常更简单可靠。
八、加强记忆
宏命令是把多个命令组合成一个命令,事务命令是在组合执行的基础上关注失败、回滚和补偿。执行按正序,撤销或补偿通常按逆序;跨系统场景要靠幂等、状态记录和补偿机制保证最终可控。