备忘录模式
备忘录模式
Rif这个模式主要用于实现“撤销/重做”(Undo/Redo)功能,或者在不破坏对象封装性的前提下保存和恢复其状态。
设计模式笔记:备忘录模式 (Memento Pattern)
一、一句话概括
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后能将该对象恢复到原先保存的状态。
二、为什么需要它 (Why - The Pain Point)
想象一下,你正在开发一个文本编辑器或一个游戏。用户可能需要撤销他们之前的操作。例如,在编辑器中写了一段文字,然后想回到几分钟前的版本;或者在游戏中,角色走错了一步,想回到上一个存档点。
要实现这个功能,你需要能够:
- 在某个时间点,保存对象(如编辑器文档、游戏角色)的当前状态。
- 在需要的时候,用之前保存的状态来恢复该对象。
直接实现会遇到什么问题?
一个天真的想法是,直接从外部访问并复制对象的所有内部字段(private 成员)。
1 | class Editor { |
痛点:破坏封装性 (Encapsulation Violation)
- 暴露内部实现:为了保存状态,
Editor类被迫将自己的私有成员变量通过public的getter和setter暴露给外部,这完全破坏了面向对象设计的封装性原则。 - 高耦合:
History类(负责保存历史记录的类)与Editor类的内部实现细节紧密耦合。如果Editor增加或删除了一个字段,History类也必须跟着修改。 - 安全性差:外部代码可以随意修改
Editor的状态,而不仅仅是保存和恢复。
核心问题:如何在不违反封装原则的情况下,实现对象状态的保存和恢复?
三、它是什么 (What - The Solution)
备忘录模式通过引入三个角色,巧妙地解决了这个问题,既能保存状态,又维护了封装性。
核心思想和角色:
- **发起人 (Originator)**:
- 这是那个我们想要保存其状态的==业务对象==(如
Editor,GameCharacter)。 - 它知道如何保存和恢复自己的状态。
- 它提供两个关键方法:
createMemento(): 创建一个备忘录(Memento) 对象,并将自己的当前状态存入其中。restore(Memento m): 接收一个备忘录对象,并用其中的状态来恢复自己。
- 这是那个我们想要保存其状态的==业务对象==(如
- **备忘录 (Memento)**:
- 这是一个状态快照对象。它的作用就是存储发起人(
Originator)的==内部状态==。 - 关键设计:它对发起人是“透明”的,即发起人可以访问其内部所有数据。但对负责人(Caretaker) 是“不透明”的,即负责人只能持有它,不能访问其内部数据。这通常通过将
Memento作为Originator的内部类或使用包级私有访问权限来实现。
- 这是一个状态快照对象。它的作用就是存储发起人(
- **负责人 (Caretaker)**:
- 它负责==保存和管理备忘录对象==,但从不关心备忘录的内部内容。
- 它就像一个仓库管理员,只负责保管箱子(Memento),但不知道箱子里装了什么。
- 它从发起人那里获取备忘录,并在需要时将其交还给发起人。它通常会用一个栈(Stack)来管理备忘录,以实现撤销/重做功能。
工作流程(实现撤销):
- 保存:客户端请求负责人保存当前状态。负责人向发起人请求一个备忘录 (
originator.createMemento()),然后将返回的备忘录存入自己的历史记录中(如压入栈)。 - 撤销:客户端请求负责人执行撤销。负责人从历史记录中取出最近的备忘忘录(如从栈中弹出一个),然后将其传递给发起人 (
originator.restore(memento))。发起人使用这个备忘录恢复自己的状态。
四、怎么做 (How - The Blueprint)
基本步骤(以文本编辑器为例):
- 创建发起人
Editor类。它有content等内部状态。 - **在
Editor内部,创建一个静态内部类EditorMemento**。这个内部类持有Editor需要保存的状态(如content)。将其构造函数和getter设为private或protected,只对外部类Editor可见。 - 在
Editor类中,实现createMemento()方法,它new一个EditorMemento并将自己的状态传入。 - 在
Editor类中,实现restore(EditorMemento memento)方法,它从传入的memento中获取状态并恢复自身。 - 创建负责人
History类。它内部有一个Stack<EditorMemento>。提供push()和pop()方法来管理备忘录。 - 客户端通过
History来协调Editor的状态保存和恢复。
Java 示例代码
1 | import java.util.Stack; |
五、UML 类图
1 | @startuml |
图示解读:
Editor(发起人) 创建EditorMemento(备忘录)。History(负责人) 持有EditorMemento的集合,但不知道其内部细节。- 实现了状态的保存,同时
Editor的内部状态content仍然是private的,封装性得到了保护。
六、优缺点分析
优点 (Pros):
- 保护封装性:将状态的保存和恢复逻辑放在发起人内部,避免了向外部暴露其实现细节。
- 简化发起人:发起人不需要关心状态的存储管理,只需负责创建和恢复,职责单一。
- 解耦:负责人只负责存储备忘录,与发起人解耦。发起人与负责人也解耦,因为它们之间通过备忘录对象间接通信。
缺点 (Cons):
- 资源消耗:如果发起人的状态非常大,或者需要频繁地保存状态,会消耗大量的内存。
- 可能暴露过多信息:如果备忘录的设计不当(比如设为
publicclass),可能会破坏封装性。 - 负责人可能难以管理:如果保存和恢复的逻辑非常复杂,负责人的代码也可能变得复杂。
七、在 Java 中的应用
java.io.Serializable: Java 的序列化机制在思想上与备忘录模式非常相似。一个对象实现了Serializable接口,就表明它可以被“快照”(序列化成字节流)。这个字节流就像一个备忘录,可以被存储在文件或网络中(由负责人管理),之后可以再反序列化,将对象恢复到原始状态。- GUI 编辑器中的撤销/重做:这是备忘录模式最经典的应用场景,几乎所有支持 Undo/Redo 的软件(如 Photoshop, IDEs, Office)都使用了这种模式或其变体。
- 数据库事务:事务的回滚(Rollback)操作也可以看作是备忘录模式的应用。在事务开始时,系统记录了数据的原始状态(备忘录),如果事务失败,就用这个状态来恢复数据。