备忘录模式(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等需要频繁撤销操作的场景
- 游戏存档系统(可控制存档数量和过期策略)
- 配置管理工具(支持配置版本回溯)
- 交易系统(保存中间状态用于回滚)
通过灵活的参数配置(最大记录数、过期时间),可以在功能和资源消耗之间取得平衡,更适合生产环境使用。