备忘录模式和命令模式在撤销场景中有什么区别?
简化版
备忘录模式通过保存状态快照来撤销,命令模式通过把操作封装成命令并提供反向操作来撤销。备忘录适合状态快照容易保存的场景,命令适合操作可逆、需要记录行为日志、队列执行的场景,两者也可以结合使用。
详细版
备忘录模式:
- 保存对象状态。
- 撤销时恢复旧状态。
- 不一定关心具体执行了什么操作。
- 快照大时内存成本高。
命令模式:
- 保存操作对象。
- 撤销时执行反向操作或回滚逻辑。
- 适合日志、排队、宏命令、重放。
- 反向操作复杂时实现困难。
结合方式:
- 命令执行前保存备忘录。
- 命令负责执行和撤销入口。
- 备忘录负责恢复状态。
完整版教学
一、备忘录撤销的是状态
备忘录模式不关心你是通过什么操作到达当前状态的。它只保存“之前是什么样”。
撤销时直接把状态恢复回去。这适合状态规模可控、快照容易保存的场景,比如表单草稿、配置对象、画布状态。
二、命令模式撤销的是操作
命令模式更关注动作。每个命令知道自己做了什么,也知道如何撤销。
比如“向右移动 10 像素”的撤销操作是“向左移动 10 像素”。这种方式不一定要保存完整对象状态,内存更省。
但如果操作不可逆,比如随机生成、复杂合并、外部系统调用,反向命令就难写。
三、两者可以结合
在复杂系统中,命令对象可以保存一个备忘录。执行命令前保存状态,撤销命令时恢复状态。
这样命令模式负责行为组织,备忘录模式负责状态恢复。
四、常见误区与工程判断
面试中不要简单说“它们都能撤销”。要讲清撤销依据不同:一个靠快照,一个靠反向操作。
工程选择时看成本:状态小、恢复简单,用备忘录;操作可逆、需要日志和重放,用命令;两者都需要时可以组合。
五、撤销方案里两种模式经常配合使用
命令模式和备忘录模式都能支持撤销,但思路不同。命令模式关注“把操作封装起来”,撤销时通常执行一个反向操作;备忘录模式关注“把状态保存下来”,撤销时恢复历史快照。前者偏行为日志,后者偏状态快照。
如果一个操作很容易反向执行,比如加 10 可以减 10,插入一段文本可以删除这段文本,命令模式比较自然。如果对象状态复杂、反向操作难写或者容易遗漏副作用,保存备忘录会更稳。比如画布对象、表单对象、游戏角色状态,直接恢复快照往往比推导反向动作可靠。
真实项目里经常组合:每个 Command 执行前让 Originator 创建 Memento,命令执行失败或撤销时恢复快照。这样命令负责表达“用户做了什么”,备忘录负责保证“能回到哪里”。面试时说出这种组合,会比单纯比较二者更有深度。
还有一个判断角度是副作用。如果操作会调用外部系统、发送消息、写日志,单纯恢复对象快照并不能撤销外部副作用;这时命令模式的操作日志、补偿动作和事务边界更重要。备忘录负责对象状态,不负责让世界倒流。
六、用工程约束检验答案
备忘录记录“操作前状态”,命令记录“如何执行与反执行”。移动坐标从 10 到 15 时,Memento 可保存 10;Command 可保存增量 +5,并在 undo 时执行 -5。前者恢复直接,后者更依赖逆操作正确性。
| 检查项 | 核心判断 | 工程含义 |
|---|---|---|
| Memento | 恢复状态快照 | 适合逆操作难描述 |
| Command | 执行/撤销请求 | 适合动作可逆且需日志化 |
| 组合 | Command 持有执行前 Memento | 复杂命令也能可靠回滚 |
把关键关系压缩成一条可复述的路径:
command.execute 前 createMemento
执行具体业务动作
undo 时取出该快照
Originator.restore 恢复
选择依据不是都能 undo,而是系统更容易可靠保存状态,还是可靠表达逆操作。
落地前可以再按下面 3 步复核:
- 先说明“Memento”的核心机制:恢复状态快照;再交代边界:适合逆操作难描述。
- 接着分析“Command”:执行/撤销请求;不能遗漏对应代价或结果:适合动作可逆且需日志化。
- 最后用“组合”检查方案:Command 持有执行前 Memento;验收时确认复杂命令也能可靠回滚。
这三项构成完整判断链:先讲清Memento,再说明Command,最后用组合检验实现是否越界。
面试中若能给出违反“复杂命令也能可靠回滚”的反例,再说明修正办法,答案就从模式定义落到了可验证的工程决策。
七、常见误区与追问
- 误区:只看到“Memento”就认为方案成立。 必须同时说明核心机制“恢复状态快照”和工程边界“适合逆操作难描述”。
- 误区:把“Command”当成无条件结论。 只有在“执行/撤销请求”成立时,才能据此讨论“适合动作可逆且需日志化”。
- 追问:命令撤销一定用逆命令吗? 不一定,也可让命令保存 Memento 并恢复。
- 追问:备忘录知道执行了什么吗? 通常不知道,它只表达某时刻的状态。
- 追问:哪种更节省空间? 增量命令常较小,但取决于参数和恢复需求,不能一概而论。
- 追问:外部副作用如何撤销? 快照不能自动撤回邮件或支付,必须设计补偿动作和幂等。
- 追问:两者组合的代价是什么? 命令对象和快照都要管理生命周期,历史结构更复杂。
八、加强记忆
记忆时抓住这条主线:备忘录靠状态快照撤销;命令靠反向操作撤销;快照大时备忘录成本高;两者可以组合实现复杂撤销。面试回答先给出模式意图,再用调用链或数据流说明角色协作,最后主动交代适用边界与工程代价。