新谈设计模式 Chapter 17 — 备忘录模式 Memento

张开发
2026/4/21 16:27:56 15 分钟阅读

分享文章

新谈设计模式 Chapter 17 — 备忘录模式 Memento
Chapter 17 — 备忘录模式 Memento灵魂速记游戏存档——随时存档随时读档回到过去。秒懂类比你打游戏 Boss 战之前先存个档。打输了读档回到存档那一刻的状态重新来。备忘录模式就是这个存档机制在不破坏封装的前提下捕获对象的内部状态以便之后恢复。问题引入// 灾难现场想保存对象状态classEditor{std::string content_;intcursorPos_;intscrollPos_;// ...还有很多私有状态};// 怎么保存// 方案1把所有字段设成 public → 破坏封装// 方案2写 getter 全部暴露出去 → 依然破坏封装// 方案3让 Editor 自己生成快照 → ✅ 备忘录模式结构┌───────────────┐ │ Originator │ ← 原发器要被保存的对象 ├───────────────┤ │ -state │ │ save()→Memento│ ← 自己创建快照 │ restore(m) │ ← 从快照恢复 └───────────────┘ ┌───────────────┐ │ Memento │ ← 快照只有 Originator 能读写内容 ├───────────────┤ │ -savedState │ └───────────────┘ ┌───────────────┐ │ Caretaker │ ← 管理者只负责保存快照不偷看内容 ├───────────────┤ │ -mementos[] │ │ save() │ │ undo() │ └───────────────┘三个角色Originator知道自己有什么状态能存能取Memento不透明的快照盒子Caretaker保管快照但不能查看也不能修改快照内容C 实现#includeiostream#includememory#includestring#includevector// 备忘录不透明的快照 classEditorMemento{public:~EditorMemento()default;private:friendclassEditor;// 只有 Editor 能访问内部EditorMemento(std::string content,intcursorPos,intscrollPos):content_(std::move(content)),cursorPos_(cursorPos),scrollPos_(scrollPos){}std::string content_;intcursorPos_;intscrollPos_;};// 原发器编辑器 classEditor{public:voidtype(conststd::stringtext){content_text;cursorPos_static_castint(text.size());std::cout ✏️ 输入: \text\\n;}voidmoveCursor(intpos){cursorPos_pos;}voidscroll(intpos){scrollPos_pos;}// 创建快照std::unique_ptrEditorMementosave()const{std::cout 存档: \content_\ cursorcursorPos_\n;// 这里用 new 而不是 make_unique因为 EditorMemento 的构造函数是 private 的。// make_unique 内部调的是 new但它是在 std::make_unique 函数内部调用// 那里没有 friend 权限。直接用 new 是在 Editor 类内部有 friend 权限。returnstd::unique_ptrEditorMemento(newEditorMemento(content_,cursorPos_,scrollPos_));}// 从快照恢复voidrestore(constEditorMementomemento){content_memento.content_;cursorPos_memento.cursorPos_;scrollPos_memento.scrollPos_;std::cout 读档: \content_\ cursorcursorPos_\n;}voidprintState()const{std::cout 当前状态: \content_\ (cursorcursorPos_, scrollscrollPos_)\n;}private:std::string content_;intcursorPos_0;intscrollPos_0;};// 管理者历史记录 classHistory{public:explicitHistory(Editoreditor):editor_(editor){}voidsave(){snapshots_.push_back(editor_.save());}voidundo(){if(snapshots_.empty()){std::cout (没有存档可以读取)\n;return;}editor_.restore(*snapshots_.back());snapshots_.pop_back();}size_tsnapshotCount()const{returnsnapshots_.size();}private:Editoreditor_;std::vectorstd::unique_ptrEditorMementosnapshots_;};intmain(){Editor editor;Historyhistory(editor);// 写文章边写边存editor.type(Hello);history.save();editor.type( World);history.save();editor.type(!!!);editor.printState();std::cout\n 撤销一次 \n;history.undo();editor.printState();std::cout\n 再撤销一次 \n;history.undo();editor.printState();std::cout\n 再撤销回到最初\n;history.undo();}输出✏️ 输入: Hello 存档: Hello cursor5 ✏️ 输入: World 存档: Hello World cursor11 ✏️ 输入: !!! 当前状态: Hello World!!! (cursor14, scroll0) 撤销一次 读档: Hello World cursor11 当前状态: Hello World (cursor11, scroll0) 再撤销一次 读档: Hello cursor5 当前状态: Hello (cursor5, scroll0) 再撤销回到最初 (没有存档可以读取)核心洞察Memento 的精髓封装不被打破。Caretaker 拿着快照却看不了里面的内容——它只是个保管箱。这就是为什么用friend classclassEditorMemento{friendclassEditor;// 只有 Editor 能读写private:// 所有成员都是 privatestd::string content_;intcursorPos_;};什么时候用✅ 适合❌ 别用需要撤销/回滚不需要回到过去需要保存对象完整状态快照状态太大快照成本高想保存状态但不破坏封装用 Command 的反向操作就够了事务回滚、检查点状态变化可以用反向操作表达⚠️内存陷阱如果对象状态很大比如位图编辑器的整张图片每次存档都是一份完整拷贝内存会炸。考虑增量快照或 Command 模式。防混淆Memento vs CommandMementoCommand撤销方式恢复快照整体还原反向操作逆向执行存储内容对象的完整状态操作本身内存开销大每次保存全量小只存操作适用场景状态复杂、难以反向计算操作可逆、状态大一句话分清Memento 拍照片存状态Command 记笔记存操作。两者可以组合使用复杂系统中Command 负责记录操作并尝试反向撤销Memento 作为兜底方案——如果反向操作太复杂直接恢复快照。

更多文章