备忘录模式(Memento):状态的"时光机"
更新时间:2026-08-25。本文按"先看不用模式的困境 → 再用模式重构 → 类图对比分析"的结构,论述备忘录模式。可运行代码:demos/design-patterns/memento。
一、问题场景:需要"回到过去"的某个状态
编辑器撤销到某历史版本、游戏读档、表单草稿、数据库事务回滚。核心对象的状态需要被保存并在以后恢复,且保存/恢复不能破坏封装。
二、不使用模式:手工备份字段 / 快照暴露内部结构
方案 A:调用方手工保存字段
cpp
// 调用方要知道 Editor 的所有字段才能备份
std::string text_ = ed.text; int cursor_ = ed.cursor;
// 恢复:逐个字段拷回 —— 字段一变,所有备份点都要改| 缺陷 | 说明 |
|---|---|
| 违反封装 | 调用方必须访问对象内部字段 |
| 违反 LoD | 备份逻辑散落在所有需要撤销的调用方 |
| 字段演进即灾难 | 对象加字段,所有备份/恢复点同步改 |
| 易漏字段 | 新字段忘备份 = 恢复后状态不一致 |
方案 B:对象自己提供备份但返回裸字段
cpp
Snapshot snap = ed.save(); // 若 Snapshot 是"裸字段袋子",内部结构仍外泄不使用模式的类图

三、使用模式:备忘录封装快照,原发者自管
备忘录模式引入三个角色:原发者(Originator,要保存状态的对象,自己负责创建/恢复备忘录)、备忘录(Memento,不可变的状态快照,对外不可读写内部)、管理者(Caretaker,只负责存/取备忘录,不碰内容)。
cpp
// memento.h
class Editor; // 前向声明
class Memento { // 备忘录:不可变快照
private:
friend class Editor; // 只有原发者可读写内部
Memento(std::string text, int cursor) : text_(std::move(text)), cursor_(cursor) {}
std::string text_;
int cursor_;
public:
// 对外只读访问器(可选)
};
class Editor { // 原发者
public:
void set(std::string text, int cursor) { text_ = std::move(text); cursor_ = cursor; }
std::unique_ptr<Memento> save() const {
return std::make_unique<Memento>(text_, cursor_); // 自己打快照
}
void restore(const Memento& m) {
text_ = m.text_; cursor_ = m.cursor_; // 从快照恢复
}
private:
std::string text_;
int cursor_;
};
// 管理者:只存不读
class History {
public:
void push(std::unique_ptr<Memento> m) { stack_.push_back(std::move(m)); }
std::unique_ptr<Memento> pop() { /* 取栈顶 */ }
private:
std::vector<std::unique_ptr<Memento>> stack_;
};
// 使用
Editor ed; History h;
ed.set("v1", 0); h.push(ed.save());
ed.set("v2", 3); h.push(ed.save());
ed.set("v3", 5);
ed.restore(*h.pop()); // 回 v2使用模式的类图

四、类图对比与分析
| 维度 | 不使用(手工备份) | 使用备忘录 |
|---|---|---|
| 封装 | 调用方直摸字段 | 快照内部私有,仅原发者可读 |
| 备份逻辑 | 散落各调用方 | 原发者 save()/restore() 一处 |
| 字段演进 | 备份点全改 | 只改原发者与备忘录 |
| 管理者职责 | 无 | 只管存/取,不碰内容(SRP) |
| 结构成本 | 无 | Memento 类 + 原发者 friend 访问 |
分析结论:备忘录把"备份-恢复"从调用方劳动变成原发者的能力,并用 friend 控制快照读写权限。代价是快照拷贝成本(每次 save 拷贝全量状态;大对象可用差分快照)与快照内存占用(撤销栈深则吃内存)。本仓库内存视角:备忘录栈是典型"历史即内存"场景,需设上限或压缩。
五、适用边界
| 适用 | 不适用 |
|---|---|
| 需要撤销/回滚/读档 | 状态简单且调用方可安全访问(直接拷贝即可) |
| 状态内部结构需对外隐藏 | 快照巨大且频繁(评估差分/命令式撤销) |
| 历史版本管理 | 撤销需求可改用命令模式的 undo(更省内存) |
与命令模式的关系:备忘录适合"恢复状态";命令模式适合"逆操作"。编辑器常用两者结合——命令记录操作,备忘录记录快照,增量命令撤销 + 快照兜底是工业界常见组合。
一句话总结
备忘录模式用不可变快照封装对象状态,原发者自管 save/restore、管理者只存不读——备份-恢复收敛到原发者、内部结构不外泄;代价是快照拷贝与内存成本,撤销栈需设上限,大对象可考虑差分快照或命令式撤销。