备忘录模式及优化
备忘录模式(Memento Pattern)是一种行为型设计模式,用于在不暴露对象内部结构的前提下,捕获并保存对象的内部状态,以便后续需要时恢复到该状态。
一、介绍
核心角色
- 原发器(Originator)
- 被保存状态的对象,负责创建备忘录(保存当前状态)和从备忘录恢复状态。
- 示例:文本编辑器、游戏角色。
- 备忘录(Memento)
- 存储原发器的内部状态,且仅允许原发器访问其内容(通常通过友元类实现)。
- 示例:编辑器的文本快照、游戏存档文件。
- 负责人(Caretaker)
- 管理备忘录的存储和获取,不直接操作备忘录的内容。
- 示例:编辑器的历史记录管理器、游戏存档管理器。
优点
- 封装性好
- 备忘录封装了对象的状态,原发器的内部实现细节不会暴露给其他对象
- 简化原发器
- 原发器不需要关心状态的保存和恢复细节,由备忘录和负责人处理
- 提供状态恢复机制
- 可以方便地回到对象之前的某个状态,实现撤销、恢复功能
- 保持状态的完整性
- 确保状态的保存和恢复是一致的,避免手动保存状态可能出现的错误
适用场景
- 需要提供撤销/恢复功能时
- 如文本编辑器的撤销操作、图像编辑软件的历史记录功能
- 游戏中的存档和读档功能
- 需要保存对象的中间状态时
- 复杂计算过程中的中间结果保存
- 事务操作中的状态记录(用于回滚)
- 不希望暴露对象的内部实现细节,但需要保存状态时
- 保护对象的封装性,通过备忘录间接访问状态
二、实现
以文本编辑器的为例,使用备忘录模式保存和恢复文本内容:
#include <iostream>
#include <string>
#include <vector>// 备忘录类:存储原发器的状态
class Memento {
private:std::string state_;// 只有原发器可以访问备忘录的状态friend class TextEditor;Memento(const std::string& state) : state_(state) {}void setState(const std::string& state) {state_ = state;}std::string getState() const {return state_;}
};// 原发器类:创建并使用备忘录
class TextEditor {
private:std::string text_;public:void setText(const std::string& text) {text_ = text;}std::string getText() const {return text_;}// 创建备忘录Memento createMemento() const {return Memento(text_);}// 从备忘录恢复状态void restoreFromMemento(const Memento& memento) {text_ = memento.getState();}
};// 负责人类:管理备忘录
class Caretaker {
private:std::vector<Memento> mementos_;TextEditor& editor_;public:Caretaker(TextEditor& editor) : editor_(editor) {}// 保存当前状态void save() {mementos_.push_back(editor_.createMemento());std::cout << "已保存状态: " << editor_.getText() << std::endl;}// 撤销到上一个状态void undo() {if (mementos_.empty()) {std::cout << "没有可恢复的状态" << std::endl;return;}Memento lastMemento = mementos_.back();mementos_.pop_back();editor_.restoreFromMemento(lastMemento);std::cout << "已恢复状态: " << editor_.getText() << std::endl;}
};// 客户端代码
int main() {TextEditor editor;Caretaker caretaker(editor);editor.setText("第一段文本");caretaker.save();editor.setText("第二段文本");caretaker.save();editor.setText("第三段文本");std::cout << "当前文本: " << editor.getText() << std::endl;caretaker.undo(); // 恢复到第二段文本caretaker.undo(); // 恢复到第一段文本caretaker.undo(); // 没有可恢复的状态return 0;
}
输出结果
已保存状态: 第一段文本
已保存状态: 第二段文本
当前文本: 第三段文本
已恢复状态: 第二段文本
已恢复状态: 第一段文本
没有可恢复的状态
应用场景
- 文本编辑器
- 保存文本的历史状态,支持撤销、重做操作
- 图形设计软件
- 保存设计过程中的各个阶段,允许用户回溯到之前的设计状态
- 数据库事务
- 保存事务执行前的状态,在事务失败时可以回滚到原始状态
- 游戏存档
- 保存游戏进度,允许玩家读取之前的存档
- 配置管理
- 保存系统配置的历史版本,便于恢复到之前的配置状态
三、优化
优化点
- 不可变备忘录
- 备忘录的状态变量
state_
声明为const
,只能在构造时初始化 - 提供只读访问接口
getState()
,避免状态被意外修改
- 备忘录的状态变量
- 泛型设计
- 使用模板实现通用备忘录和原发器,支持任意状态类型(字符串、自定义对象等)
- 无需为每种状态类型重复实现备忘录模式
- 增量状态更新
- 通过
updateState
方法支持增量修改,避免全量替换状态 - 适合大型状态对象,减少数据复制开销
- 通过
- 生命周期管理
- 限制最大历史记录数量(
maxHistorySize_
) - 设置过期时间(
maxAge_
),自动清理旧记录 - 提供
cleanUp
方法主动维护历史记录
- 限制最大历史记录数量(
- 时间戳追踪
- 每个备忘录包含创建时间戳,用于过期判断
- 支持按时间维度管理历史记录
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <chrono>
#include <sstream>
#include <algorithm>// 1. 泛型不可变备忘录基类
template <typename StateType>
class Memento {
protected:const StateType state_; // 不可变状态const std::chrono::system_clock::time_point timestamp_; // 时间戳用于过期管理// 仅允许原发器创建备忘录explicit Memento(StateType state) : state_(std::move(state)), timestamp_(std::chrono::system_clock::now()) {}public:virtual ~Memento() = default;// 只读访问状态const StateType& getState() const {return state_;}// 获取时间戳(用于清理过期状态)std::chrono::system_clock::time_point getTimestamp() const {return timestamp_;}// 友元声明:允许对应原发器访问私有构造函数template <typename T>friend class Originator;
};// 2. 泛型原发器类
template <typename StateType>
class Originator {
private:StateType currentState_;public:explicit Originator(StateType initialState) : currentState_(std::move(initialState)) {}// 设置当前状态void setState(StateType newState) {currentState_ = std::move(newState);}// 获取当前状态const StateType& getState() const {return currentState_;}// 创建备忘录(保存当前状态)std::unique_ptr<Memento<StateType>> createMemento() const {return std::make_unique<Memento<StateType>>(currentState_);}// 从备忘录恢复状态void restoreFromMemento(const Memento<StateType>& memento) {currentState_ = memento.getState();}// 增量更新(只保存变化的部分)void updateState(const std::function<void(StateType&)>& updater) {updater(currentState_);}
};// 3. 增强型负责人类(带生命周期管理)
template <typename StateType>
class Caretaker {
private:std::vector<std::unique_ptr<Memento<StateType>>> mementos_;Originator<StateType>& originator_;size_t maxHistorySize_; // 最大历史记录数std::chrono::minutes maxAge_; // 最大保存时长public:Caretaker(Originator<StateType>& originator, size_t maxHistorySize = 100, std::chrono::minutes maxAge = std::chrono::minutes(60)): originator_(originator),maxHistorySize_(maxHistorySize),maxAge_(maxAge) {}// 保存当前状态并清理过期记录void save() {auto memento = originator_.createMemento();mementos_.push_back(std::move(memento));cleanUp(); // 保存后立即清理std::cout << "已保存状态: " << originator_.getState() << std::endl;}// 撤销到上一个状态bool undo() {if (mementos_.empty()) {std::cout << "没有可恢复的状态" << std::endl;return false;}auto lastMemento = std::move(mementos_.back());mementos_.pop_back();originator_.restoreFromMemento(*lastMemento);std::cout << "已恢复状态: " << originator_.getState() << std::endl;return true;}// 清理过期或超量的记录void cleanUp() {auto now = std::chrono::system_clock::now();// 移除过期记录auto it = std::remove_if(mementos_.begin(), mementos_.end(),[&](const std::unique_ptr<Memento<StateType>>& m) {auto age = std::chrono::duration_cast<std::chrono::minutes>(now - m->getTimestamp());return age > maxAge_;});mementos_.erase(it, mementos_.end());// 移除超出最大数量的旧记录while (mementos_.size() > maxHistorySize_) {mementos_.erase(mementos_.begin()); // 移除最早的记录}}// 获取当前历史记录数量size_t getHistoryCount() const {return mementos_.size();}
};// 4. 客户端示例:文本编辑器应用
int main() {// 初始化编辑器(状态类型为字符串)Originator<std::string> editor("初始文本");Caretaker<std::string> caretaker(editor, 5, std::chrono::minutes(5)); // 最多保存5条,5分钟过期// 操作示例editor.updateState([](std::string& s) { s += " - 第一段修改"; });caretaker.save();editor.updateState([](std::string& s) { s += " - 第二段修改"; });caretaker.save();editor.updateState([](std::string& s) { s += " - 第三段修改"; });std::cout << "当前状态: " << editor.getState() << std::endl;// 撤销操作caretaker.undo(); // 恢复到第二段修改caretaker.undo(); // 恢复到第一段修改caretaker.undo(); // 恢复到初始文本caretaker.undo(); // 没有可恢复的状态return 0;
}
输出结果
已保存状态: 初始文本 - 第一段修改
已保存状态: 初始文本 - 第一段修改 - 第二段修改
当前状态: 初始文本 - 第一段修改 - 第二段修改 - 第三段修改
已恢复状态: 初始文本 - 第一段修改 - 第二段修改
已恢复状态: 初始文本 - 第一段修改
已恢复状态: 初始文本
没有可恢复的状态
适用场景扩展
优化后的备忘录模式特别适合:
- 文本编辑器、代码IDE等需要频繁撤销操作的场景
- 游戏存档系统(可控制存档数量和过期策略)
- 配置管理工具(支持配置版本回溯)
- 交易系统(保存中间状态用于回滚)
通过灵活的参数配置(最大记录数、过期时间),可以在功能和资源消耗之间取得平衡,更适合生产环境使用。