C 备忘录模式Memento Pattern一、模式基础概述1.1 定义备忘录模式属于行为型设计模式在不破坏对象封装性的前提下捕获并保存一个对象的内部状态后续可将对象恢复到之前保存的状态。常用来实现撤销、重做、存档、状态回滚等功能。1.2 核心思想分离状态保存、状态持有、状态恢复三类职责对象内部状态对外隐藏仅自身可读写保证数据安全通过中间备忘录对象完成状态快照负责人统一管理历史记录。1.3 设计原则遵循封装原则、单一职责原则不对外暴露原发器私有成员。二、核心角色与职责备忘录模式包含三大核心角色角色间访问权限严格隔离角色名称英文名称核心职责访问权限关键方法原发器Originator业务主体对象1. 创建备忘录保存自身当前状态2. 从备忘录恢复历史状态可读写备忘录内部数据createMemento()restore(Memento*)备忘录Memento纯数据容器仅存储原发器状态快照仅原发器可访问内部成员外部类无法创建、修改私有成员变量状态数据私有构造函数负责人Caretaker管理备忘录集合1. 保存历史快照2. 提供撤销、重做、清空历史功能仅持有备忘录指针/对象不能读取/修改内部状态save()undo()redo()clearHistory()2.1 角色协作流程原发器调用createMemento()生成包含当前状态的备忘录对象负责人将备忘录存入历史列表完成状态存档执行撤销/重做时负责人取出对应备忘录原发器调用restore()依据备忘录数据还原自身状态全程负责人只做存储与调度不触碰状态数据。2.2 依赖关系原发器 ↔ 备忘录友元关系唯一合法访问通道负责人 → 备忘录仅持有引用无访问内部权限负责人 → 原发器调用原发器方法完成存档与恢复。三、核心实现要点C 专属友元friend备忘录将原发器声明为友元保证只有原发器能读写私有状态严格保护封装。私有构造函数备忘录构造函数设为private外部无法实例化只能由原发器创建。禁用拷贝/赋值防止备忘录对象被意外复制、篡改使用 delete禁用拷贝构造与赋值运算符。智能指针管理使用std::unique_ptr/std::shared_ptr管理备忘录生命周期避免内存泄漏。深拷贝若状态包含指针、容器等复杂结构必须做深拷贝避免浅拷贝引发数据错乱。四、完整 C 代码实现4.1 业务场景模拟游戏角色状态存档/读档角色拥有生命值、攻击力、位置坐标实现存档、多步撤销回档功能。#includeiostream#includevector#includememory#includecmath// 前向声明classGameRole;// 1. 备忘录 Memento状态快照 classRoleMemento{// 友元仅游戏角色可以访问内部状态friendclassGameRole;private:// 角色状态数据inthp_;// 生命值intattack_;// 攻击力doubleposX_;// X坐标doubleposY_;// Y坐标// 私有构造函数外部无法创建备忘录RoleMemento(inthp,intatk,doublex,doubley):hp_(hp),attack_(atk),posX_(x),posY_(y){}// 禁用拷贝构造与赋值防止外部篡改RoleMemento(constRoleMemento)delete;RoleMementooperator(constRoleMemento)delete;};// 2. 原发器 Originator游戏角色 classGameRole{private:// 当前角色状态inthp_;intattack_;doubleposX_;doubleposY_;public:// 构造初始化角色属性GameRole(inthp,intatk,doublex,doubley):hp_(hp),attack_(atk),posX_(x),posY_(y){}// 角色行为受到伤害voidhurt(intdamage){hp_std::max(0,hp_-damage);std::cout角色受到 damage 点伤害\n;}// 角色行为移动位置voidmove(doublex,doubley){posX_x;posY_y;std::cout角色移动到坐标 (x, y)\n;}// 创建备忘录保存当前完整状态std::unique_ptrRoleMementocreateMemento()const{returnstd::make_uniqueRoleMemento(hp_,attack_,posX_,posY_);}// 从备忘录恢复状态voidrestore(constRoleMemento*memento){if(!memento)return;hp_memento-hp_;attack_memento-attack_;posX_memento-posX_;posY_memento-posY_;}// 打印当前角色状态voidshowStatus()const{std::cout【角色状态】生命值hp_ 攻击力attack_ 坐标(posX_, posY_)\n\n;}};// 3. 负责人 Caretaker存档管理器 classSaveManager{private:// 存储所有历史存档快照std::vectorstd::unique_ptrRoleMementosaveList_;intcurrentIndex_-1;// 当前所处存档位置constintMAX_SAVE10;// 最大存档数量限制内存占用public:// 保存当前状态为新存档voidsave(GameRolerole){// 撤销后重新操作删除后续无效存档if(currentIndex_(int)saveList_.size()-1){saveList_.erase(saveList_.begin()currentIndex_1,saveList_.end());}// 限制最大存档数if((int)saveList_.size()MAX_SAVE){saveList_.erase(saveList_.begin());currentIndex_--;}saveList_.push_back(role.createMemento());currentIndex_(int)saveList_.size()-1;std::cout 状态已存档\n;}// 撤销回退到上一存档boolundo(GameRolerole){if(currentIndex_0){std::cout 无法撤销已到最早记录\n;returnfalse;}currentIndex_--;role.restore(saveList_[currentIndex_].get());std::cout 撤销成功\n;returntrue;}// 重做前进到下一存档boolredo(GameRolerole){if(currentIndex_(int)saveList_.size()-1){std::cout 无法重做已到最新记录\n;returnfalse;}currentIndex_;role.restore(saveList_[currentIndex_].get());std::cout 重做成功\n;returntrue;}// 清空所有存档voidclearAll(){saveList_.clear();currentIndex_-1;std::cout 所有存档已清空\n;}};// 客户端测试 intmain(){// 创建游戏角色 存档管理器GameRolehero(100,50,0.0,0.0);SaveManager saveMgr;std::cout 初始状态 std::endl;hero.showStatus();saveMgr.save(hero);// 第一次操作受伤hero.hurt(30);hero.showStatus();saveMgr.save(hero);// 第二次操作移动位置hero.move(10.5,20.8);hero.showStatus();saveMgr.save(hero);// 执行撤销std::cout 执行第一次撤销 std::endl;saveMgr.undo(hero);hero.showStatus();std::cout 执行第二次撤销 std::endl;saveMgr.undo(hero);hero.showStatus();// 执行重做std::cout 执行重做 std::endl;saveMgr.redo(hero);hero.showStatus();// 尝试超出边界撤销saveMgr.undo(hero);saveMgr.undo(hero);return0;}4.2 运行输出 初始状态 【角色状态】生命值100 攻击力50 坐标(0, 0) 状态已存档 角色受到 30 点伤害 【角色状态】生命值70 攻击力50 坐标(0, 0) 状态已存档 角色移动到坐标 (10.5, 20.8) 【角色状态】生命值70 攻击力50 坐标(10.5, 20.8) 状态已存档 执行第一次撤销 撤销成功 【角色状态】生命值70 攻击力50 坐标(0, 0) 执行第二次撤销 撤销成功 【角色状态】生命值100 攻击力50 坐标(0, 0) 执行重做 重做成功 【角色状态】生命值70 攻击力50 坐标(0, 0) 无法撤销已到最早记录 无法撤销已到最早记录五、适用场景与禁用场景5.1 适用场景需要状态回滚编辑器、绘图软件、APP 操作记录实现撤销/重做功能。数据存档与恢复游戏存档、配置快照、用户表单临时保存、系统运行快照。事务回滚业务操作异常时恢复到操作前状态。需要保护封装需保存对象内部状态但不允许外部直接访问私有成员。5.2 不适用场景对象状态极其庞大频繁快照会造成严重内存占用状态简单、无需历史记录引入模式属于过度设计状态频繁变更且无回滚需求。六、模式优缺点6.1 优点封装性极佳对象内部状态完全对外隐藏仅自身可读写数据安全。职责划分清晰原发器处理业务备忘录存储数据负责人管理历史记录各司其职。灵活实现多级回滚可自由保存多份历史快照支持无限级撤销、重做。状态恢复可靠由原发器自主完成恢复逻辑避免外部篡改导致异常。6.2 缺点内存开销大每一次存档都会生成新备忘录对象状态复杂时内存压力显著。拷贝成本高包含容器、指针、复杂嵌套对象时深拷贝会消耗 CPU 性能。历史记录难以清理不限制存档数量会导致内存持续上涨。类数量增多新增备忘录、负责人类小幅提升代码复杂度。七、C 工程实践规范与优化方案7.1 编码规范必加虚析构若存在继承关系基类添加虚析构函数防止智能指针析构泄漏。严格控制访问权限备忘录构造、成员变量全部设为private仅通过友元开放权限。禁用拷贝与赋值备忘录默认禁止拷贝防止状态被意外复制篡改。优先使用智能指针全程使用std::unique_ptr管理备忘录减少裸指针。7.2 性能优化方案限制最大存档数负责人设置上限自动淘汰最早记录控制内存大小。增量快照进阶仅保存变更部分状态而非全量拷贝降低拷贝开销。延迟销毁低频操作场景可缓存常用备忘录减少重复创建。多线程防护多线程环境下对存档列表、状态读写加互斥锁保证线程安全。7.3 复杂状态处理状态含指针/动态容器必须执行深拷贝状态为只读配置可复用备忘录对象不必每次新建。八、相关模式对比8.1 备忘录模式 VS 命令模式对比项备忘录模式命令模式核心目标保存对象状态快照按状态回滚封装操作行为反向执行操作实现撤销存储内容数据状态操作指令、执行者撤销逻辑直接还原历史状态执行反向操作适用场景整体状态回滚存档、编辑器动作序列、任务队列、批量操作8.2 备忘录模式 VS 原型模式原型侧重对象快速克隆用于对象创建备忘录侧重状态快照与回滚用于历史记录二者用途完全不同。九、总结备忘录模式是状态快照与回滚的专用设计模式核心亮点是在不破坏封装的前提下实现状态保存与恢复。核心结构原发器 备忘录 负责人三者配合C 实现关键友元、私有构造、禁用拷贝、智能指针、深拷贝使用建议适合撤销、存档、回滚类场景务必控制快照数量规避内存与性能问题选型提醒简单状态回滚优先使用本模式复杂动作撤销可结合命令模式。