备忘录模式
引言
在软件系统中,当需要记录对象状态变化(如文档编辑的历史记录)或支持状态回滚(如事务操作撤销)时,直接操作状态会导致逻辑耦合和历史管理混乱。备忘录模式通过封装状态快照与分离状态管理,实现"时光机"般的状态回溯能力,成为状态管理的优雅解决方案。
诞生背景
GoF在《设计模式》中提出备忘录模式,解决三大痛点:
- 状态暴露:对象内部状态直接暴露破坏封装性
- 历史管理复杂:客户端需自行管理状态历史记录
- 回滚耦合:状态恢复逻辑与业务代码高度耦合
演进过程
- GoF基础(1994):确立三核心角色(发起人、备忘录、管理者)
- 现代应用扩展:数据库事务日志、浏览器历史记录管理
- 跨领域应用:游戏存档系统、分布式系统状态快照
核心概念
- 发起人(Originator)
- 创建备忘录保存自身状态
- 从备忘录恢复历史状态
- 备忘录(Memento)
- 存储发起人对象的内部状态快照
- 管理者(Caretaker)
- 负责保存和管理备忘录对象
通用实现
Java 实现
java
// 备忘录:存储编辑器状态
class EditorMemento {
private final String content;
public EditorMemento(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
// 发起人:文本编辑器
class TextEditor {
private String content;
public void write(String text) {
content = text;
}
public EditorMemento save() {
return new EditorMemento(content);
}
public void restore(EditorMemento memento) {
content = memento.getContent();
}
public void print() {
System.out.println("Current content: " + content);
}
}
// 管理者:历史记录管理
class HistoryManager {
private final Stack<EditorMemento> history = new Stack<>();
public void saveState(EditorMemento memento) {
history.push(memento);
}
public EditorMemento undo() {
if (!history.isEmpty()) {
return history.pop();
}
return null;
}
}
// 客户端使用
public class Client {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
HistoryManager history = new HistoryManager();
editor.write("First draft");
history.saveState(editor.save());
editor.write("Second version");
editor.restore(history.undo()); // 恢复到第一版
editor.print(); // 输出: Current content: First draft
}
}PHP 实现
php
// 备忘录:存储购物车状态
class CartMemento {
private array $items;
public function __construct(array $items) {
$this->items = $items;
}
public function getItems(): array {
return $this->items;
}
}
// 发起人:购物车
class ShoppingCart {
private array $items = [];
public function addItem(string $item): void {
$this->items[] = $item;
}
public function save(): CartMemento {
return new CartMemento($this->items);
}
public function restore(CartMemento $memento): void {
$this->items = $memento->getItems();
}
public function showItems(): void {
echo "Cart items: " . implode(', ', $this->items) . "\n";
}
}
// 管理者:状态管理
class CartHistory {
private array $history = [];
public function saveState(CartMemento $memento): void {
$this->history[] = $memento;
}
public function undo(): ?CartMemento {
return array_pop($this->history);
}
}
// 客户端使用
$cart = new ShoppingCart();
$history = new CartHistory();
$cart->addItem("Laptop");
$history->saveState($cart->save());
$cart->addItem("Phone");
if ($previousState = $history->undo()) {
$cart->restore($previousState);
}
$cart->showItems(); // 输出: Cart items: Laptop应用场景
- 撤销/重做功能:文本编辑器、图形设计软件
- 事务回滚:数据库操作中的状态恢复
- 游戏存档:保存和恢复游戏进度
- 配置管理:系统配置的历史版本管理
- 工作流状态:业务流程的状态快照
案例:图形编辑器
Java 实现
java
// 备忘录:存储图形状态
class GraphicMemento {
private final String color;
private final int size;
public GraphicMemento(String color, int size) {
this.color = color;
this.size = size;
}
public String getColor() { return color; }
public int getSize() { return size; }
}
// 发起人:图形对象
class Graphic {
private String color;
private int size;
public void setProperties(String color, int size) {
this.color = color;
this.size = size;
}
public GraphicMemento save() {
return new GraphicMemento(color, size);
}
public void restore(GraphicMemento memento) {
this.color = memento.getColor();
this.size = memento.getSize();
}
public void describe() {
System.out.printf("Color: %s, Size: %dpx\n", color, size);
}
}
// 客户端使用
public class GraphicEditor {
public static void main(String[] args) {
Graphic circle = new Graphic();
Stack<GraphicMemento> history = new Stack<>();
circle.setProperties("Red", 100);
history.push(circle.save());
circle.setProperties("Blue", 150); // 修改属性
circle.restore(history.pop()); // 撤销修改
circle.describe(); // 输出: Color: Red, Size: 100px
}
}PHP 实现
php
// 备忘录:存储用户设置
class UserSettingsMemento {
private string $theme;
private int $fontSize;
public function __construct(string $theme, int $fontSize) {
$this->theme = $theme;
$this->fontSize = $fontSize;
}
public function getTheme(): string {
return $this->theme;
}
public function getFontSize(): int {
return $this->fontSize;
}
}
// 发起人:用户设置
class UserSettings {
private string $theme = 'light';
private int $fontSize = 12;
public function changeSettings(string $theme, int $fontSize): void {
$this->theme = $theme;
$this->fontSize = $fontSize;
}
public function save(): UserSettingsMemento {
return new UserSettingsMemento($this->theme, $this->fontSize);
}
public function restore(UserSettingsMemento $memento): void {
$this->theme = $memento->getTheme();
$this->fontSize = $memento->getFontSize();
}
public function display(): void {
echo "Theme: {$this->theme}, Font Size: {$this->fontSize}px\n";
}
}
// 客户端使用
$settings = new UserSettings();
$history = [];
$settings->changeSettings('dark', 14);
$history[] = $settings->save(); // 保存状态1
$settings->changeSettings('blue', 16); // 修改设置
$settings->restore(end($history)); // 恢复到状态1
$settings->display(); // 输出: Theme: dark, Font Size: 14px优点
- 状态封装:不暴露对象实现细节
- 简化发起人:移除状态管理职责
- 时间点恢复:支持任意历史状态恢复
- 职责分离:状态管理独立于业务逻辑
缺点
- 内存消耗:大量状态快照占用内存
- 性能开销:频繁保存状态影响性能
- 复杂克隆:深拷贝复杂对象可能困难
- 状态暴露风险:管理者可能误操作备忘录内部状态
扩展
- 增量备忘录:
- 只存储状态变化部分而非完整对象
- 持久化备忘录:
- 将状态快照保存到数据库或文件系统
- 多级撤销栈:
- 实现无限级撤销/重做功能
- 状态压缩:
- 定期清理不必要的历史状态
模式协作
- 与命令模式:实现可撤销操作(命令保存执行前的备忘录)
- 与原型模式:使用原型克隆创建备忘录
- 与责任链模式:多级状态恢复(如不同版本的状态管理)
- 与迭代器模式:遍历历史状态集合
延伸思考
- 数据库事务:事务日志本质是备忘录模式的工业级实现
- 版本控制系统:Git等工具的核心是备忘录模式的扩展应用
- 前端状态管理:Redux中的时间旅行调试基于备忘录原理
- 内存优化策略:使用差异存储(只保存变化部分)减少内存占用
- 快照安全:敏感数据在备忘录中的加密处理
总结
备忘录模式是对象状态的时间胶囊,通过封装状态快照与分离状态管理,实现状态保存与恢复的优雅解耦。其核心价值在于:状态封装的保护机制与历史管理的时空隧道。在需要撤销操作、状态回溯或事务管理的场景中,备忘录模式能显著提升系统的灵活性和可靠性,成为软件设计中不可或缺的"时光机器"。