备忘录模式
1. 什么是备忘录模式?
想象一下你在玩一个有存档功能的游戏。当你觉得当前进度不错,或者要进行一个有风险的操作前,你会选择“存档”。这个“存档”就保存了你当前游戏的所有状态(比如角色位置、等级、物品栏等)。如果后续操作失败或者你想回到之前的状态,你就可以“读档”,恢复到存档时的状态。
备忘录模式 就是这样一种行为型设计模式,它的核心思想是:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
简单来说,它提供了一种状态保存和恢复的机制,允许你“撤销”操作或回到历史某个时间点的状态。
2. 备忘录模式的结构 (主要角色):
Originator (发起人/源对象):
这是我们想要保存其状态的对象。
它知道如何创建备忘录 (Memento) 来保存其当前状态。
它也知道如何从备忘录中恢复其之前的状态。
重要: Originator 内部的状态通常是私有的,不直接暴露给外部。
Memento (备忘录):
这是一个简单的对象,用于存储 Originator 的内部状态。
核心特点:
它对 Originator 是“宽接口”:Originator 可以访问 Memento 内部的所有数据,以便保存和恢复状态。
它对其他对象(特别是 Caretaker)是“窄接口”:Caretaker 只能持有 Memento 对象,但不能访问或修改 Memento 的内部状态。这是为了保护封装性。
Memento 自身不应该包含任何业务逻辑,它只是一个状态的快照容器。
Caretaker (负责人/管理者):
负责保存 Memento 对象。
它不知道 Memento 内部的具体内容,也不关心。它只是像保管箱一样持有 Memento。
当需要恢复状态时,它会将之前保存的 Memento 交还给 Originator。
Caretaker 可以保存多个 Memento,从而实现多步撤销或历史记录功能。
结构图示:
+-----------------+ creates +-----------------+ | Originator |<------------------| Memento | | - state | | - state | | + setState() | | + getState() | <-- (Usually package-private or accessible only to Originator) | + createMemento()| | | | + restore(m) | +-----------------+ +-----------------+ ^ | | (holds) | uses | V | +-----------------+ | | Caretaker |-------------------------+ | - mementoList | | + saveMemento(m)| | + getMemento(idx)| +-----------------+
实现窄接口和宽接口的技巧:
在 Java 中,为了实现 Memento 对 Originator 的宽接口和对 Caretaker 的窄接口,有几种常见做法:
内部类 (Inner Class):
将 Memento 定义为 Originator 的一个内部类(甚至是私有静态内部类)。
这样,Originator 可以访问 Memento 的所有成员(即使是私有的),而 Caretaker 只能通过 Memento 暴露的公共接口(如果有的话,但通常 Memento 对 Caretaker 没有任何有意义的公共方法)或者仅仅是持有 Memento 的引用。
包级私有 (Package-Private) 接口:
定义一个接口 MementoInterface,只包含 Caretaker 需要的方法(通常是空的,或者只有标记作用)。
让 Memento 类实现这个接口,并且 Memento 类的 getState() 等方法设置为包级私有或 protected。
Originator 和 Memento 放在同一个包下,这样 Originator 可以访问 Memento 的包级私有成员。Caretaker 只能通过 MementoInterface 来引用 Memento。
标记接口 (Marker Interface) + 封装:
Memento 实现一个空的标记接口。
Memento 的状态获取方法对 Originator 可见(例如,通过构造函数传入,或者 Originator 具有特殊权限访问)。
内部类是最常见且简洁的实现方式。
3. 备忘录模式的优缺点:
优点:
封装性: 保持了 Originator 内部状态的封装。Caretaker 和其他客户端代码不需要知道 Originator 的内部结构。
简化 Originator: Originator 不需要自己管理其历史状态,将状态的保存和恢复逻辑与自身核心业务逻辑解耦。
状态恢复: 提供了方便的状态恢复机制,可以实现撤销/重做、回滚等功能。
高内聚,低耦合: Originator 和 Memento 紧密相关,但它们与 Caretaker 之间的耦合度较低。
缺点:
资源消耗: 如果 Originator 的状态非常复杂,或者需要保存大量的历史状态,那么创建和存储 Memento 对象可能会消耗大量的内存。需要谨慎管理 Memento 的生命周期。
实现细节: 如果 Originator 的内部状态非常多,创建 Memento 的时候需要复制所有相关状态,可能会比较繁琐。
可能破坏封装(如果 Memento 接口设计不当): 如果 Memento 暴露了过多的内部状态给非 Originator 对象,可能会破坏 Originator 的封装性。所以 Memento 的接口设计很重要。
4. 备忘录模式的应用场景:
需要保存和恢复对象历史状态的场景。
实现撤销/重做 (Undo/Redo) 功能: 文本编辑器、绘图软件、IDE 中的操作撤销。
数据库事务的回滚 (Rollback) 机制的简化模型。
游戏存档和读档功能。
配置信息的快照和恢复: 当用户修改配置后,可以恢复到之前的某个配置版本。
状态机中,需要回溯到某个先前状态时。
代码示例 (使用内部类实现 Memento):
假设我们有一个文本编辑器 Editor,它可以输入文本,并支持撤销操作。
import java.util.Stack; // Memento (备忘录) - 作为 Editor 的静态内部类 class Editor { private String content; // Originator 的状态 public Editor() { this.content = ""; } public void type(String words) { this.content += words; } public String getContent() { return content; } // 创建备忘录,保存当前状态 public EditorMemento save() { return new EditorMemento(this.content); } // 从备忘录恢复状态 public void restore(EditorMemento memento) { if (memento != null) { this.content = memento.getSavedContent(); } } // Memento 内部类 // 只有 Editor 类可以访问其 getSavedContent() 方法(如果设置为 private,则通过 Editor 访问) // 或者可以设为包级私有,或者像这里一样,通过外部类间接控制访问 public static class EditorMemento { // 为了简单,这里设为 public static 内部类 private final String savedContent; // Memento 存储的状态 private EditorMemento(String contentToSave) { this.savedContent = contentToSave; } // 这个方法理论上应该只被 Originator (Editor) 调用 // 如果 EditorMemento 不是 public static,而是非静态内部类,Editor可以直接访问 savedContent // 如果是 private static 内部类,Editor 也可以访问 // 这里为了Caretaker能从Editor获取,Editor能从Caretaker设置,做了一些简化 private String getSavedContent() { return savedContent; } } } // Caretaker (负责人) class History { private Stack<Editor.EditorMemento> history = new Stack<>(); // 用栈来保存历史记录 public void save(Editor editor) { history.push(editor.save()); // 从 Originator 获取 Memento 并保存 } public void undo(Editor editor) { if (!history.isEmpty()) { Editor.EditorMemento lastMemento = history.pop(); // 取出最近的 Memento editor.restore(lastMemento); // Originator 从 Memento 恢复状态 } else { System.out.println("Nothing to undo."); } } } public class MementoPatternDemo { public static void main(String[] args) { Editor editor = new Editor(); History history = new History(); // 第一次操作 editor.type("This is the first sentence. "); history.save(editor); // 保存状态1 System.out.println("Current Content: " + editor.getContent()); // 第二次操作 editor.type("This is the second sentence. "); history.save(editor); // 保存状态2 System.out.println("Current Content: " + editor.getContent()); // 第三次操作 editor.type("And this is the third one."); // history.save(editor); // 假设这次忘记保存了 System.out.println("Current Content: " + editor.getContent()); // 执行撤销 history.undo(editor); // 撤销到状态2 System.out.println("After first undo: " + editor.getContent()); history.undo(editor); // 撤销到状态1 System.out.println("After second undo: " + editor.getContent()); history.undo(editor); // 没有更多可撤销的了 System.out.println("After third undo (should be no change or message): " + editor.getContent()); } }
在这个例子中:
Editor 是 Originator。
EditorMemento 是 Memento,作为 Editor 的静态内部类。它的 savedContent 字段和构造函数是私有的(或者包级私有),getSavedContent() 是私有的(理论上),确保只有 Editor 能创建和解释它。
History 是 Caretaker,它使用一个 Stack 来存储 EditorMemento 对象,实现了多步撤销。
总结:
备忘录模式是一种强大的行为模式,它通过将对象的状态封装在备忘录中,实现了状态的外部存储和恢复,同时又不破坏对象的封装性。它在需要撤销/重做、历史记录或状态快照的场景中非常有用。关键在于设计好 Memento 的接口,使其对 Originator 开放足够的信息,而对其他对象保持封闭。