备忘录模式 (Memento Pattern)
概述
备忘录模式是一种行为型设计模式,它在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
意图
- 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态
- 这样以后就可将该对象恢复到原先保存的状态
适用场景
- 必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态
- 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性
结构
┌─────────────┐ ┌─────────────┐
│ Caretaker │──────────>│ Memento │
├─────────────┤ ├─────────────┤
│ - memento │ │ - state │
└─────────────┘ │ + getState() │
│ + setState() │
└─────────────┘
▲
│
┌─────────────┐ ┌─────────────┐
│ Originator│<─────────│ Client │
├─────────────┤ ├─────────────┤
│ - state │ │ │
│ + createMemento()│ └─────────────┘
│ + restoreFromMemento()│
└─────────────┘
参与者
- Memento:备忘录存储原发器对象的内部状态,原发器根据需要决定备忘录存储原发器的哪些内部状态
- Originator:原发器创建一个备忘录,用以记录当前时刻它的内部状态,并使用备忘录恢复内部状态
- Caretaker:负责人负责保存好备忘录,但是不能对备忘录的内容进行操作或检查
示例代码
下面是一个完整的备忘录模式示例,以游戏角色状态保存为例:
java
// Memento - 备忘录类
public class GameRoleMemento {
private int vitality; // 生命力
private int attack; // 攻击力
private int defense; // 防御力
public GameRoleMemento(int vitality, int attack, int defense) {
this.vitality = vitality;
this.attack = attack;
this.defense = defense;
}
public int getVitality() {
return vitality;
}
public int getAttack() {
return attack;
}
public int getDefense() {
return defense;
}
}
// Originator - 原发器类
public class GameRole {
private int vitality; // 生命力
private int attack; // 攻击力
private int defense; // 防御力
// 初始化游戏角色
public void init() {
this.vitality = 100;
this.attack = 100;
this.defense = 100;
}
// 战斗
public void fight() {
this.vitality = 0;
this.attack = 0;
this.defense = 0;
}
// 保存角色状态
public GameRoleMemento saveState() {
return new GameRoleMemento(vitality, attack, defense);
}
// 恢复角色状态
public void restoreState(GameRoleMemento memento) {
this.vitality = memento.getVitality();
this.attack = memento.getAttack();
this.defense = memento.getDefense();
}
// 显示状态
public void displayState() {
System.out.println("角色当前状态:");
System.out.println("生命力: " + vitality);
System.out.println("攻击力: " + attack);
System.out.println("防御力: " + defense);
}
}
// Caretaker - 负责人类
public class RoleStateCaretaker {
private GameRoleMemento memento;
public void saveMemento(GameRoleMemento memento) {
this.memento = memento;
}
public GameRoleMemento getMemento() {
return memento;
}
}
// Client - 客户端
public class Client {
public static void main(String[] args) {
// 创建游戏角色
GameRole role = new GameRole();
// 初始化角色
role.init();
System.out.println("------ 初始状态 ------");
role.displayState();
// 保存状态
RoleStateCaretaker caretaker = new RoleStateCaretaker();
caretaker.saveMemento(role.saveState());
// 战斗
role.fight();
System.out.println("\n------ 战斗后状态 ------");
role.displayState();
// 恢复状态
role.restoreState(caretaker.getMemento());
System.out.println("\n------ 恢复后状态 ------");
role.displayState();
}
}
另一个示例 - 文本编辑器
java
import java.util.ArrayList;
import java.util.List;
// Memento - 备忘录类
public class TextEditorMemento {
private String content;
private int cursorPosition;
public TextEditorMemento(String content, int cursorPosition) {
this.content = content;
this.cursorPosition = cursorPosition;
}
public String getContent() {
return content;
}
public int getCursorPosition() {
return cursorPosition;
}
}
// Originator - 原发器类
public class TextEditor {
private String content = "";
private int cursorPosition = 0;
public void type(String text) {
content += text;
cursorPosition += text.length();
}
public void delete(int length) {
int startPos = Math.max(0, cursorPosition - length);
content = content.substring(0, startPos) + content.substring(cursorPosition);
cursorPosition = startPos;
}
public void moveCursor(int position) {
cursorPosition = Math.max(0, Math.min(content.length(), position));
}
public TextEditorMemento save() {
return new TextEditorMemento(content, cursorPosition);
}
public void restore(TextEditorMemento memento) {
this.content = memento.getContent();
this.cursorPosition = memento.getCursorPosition();
}
public void display() {
System.out.println("内容: \"" + content + "\"");
System.out.println("光标位置: " + cursorPosition);
}
}
// Caretaker - 负责人类
public class TextEditorHistory {
private List<TextEditorMemento> history = new ArrayList<>();
private int currentIndex = -1;
public void save(TextEditorMemento memento) {
// 如果当前不是最新状态,删除后面的历史记录
if (currentIndex < history.size() - 1) {
history = history.subList(0, currentIndex + 1);
}
history.add(memento);
currentIndex++;
}
public TextEditorMemento undo() {
if (currentIndex > 0) {
currentIndex--;
return history.get(currentIndex);
}
return null;
}
public TextEditorMemento redo() {
if (currentIndex < history.size() - 1) {
currentIndex++;
return history.get(currentIndex);
}
return null;
}
public boolean canUndo() {
return currentIndex > 0;
}
public boolean canRedo() {
return currentIndex < history.size() - 1;
}
}
// Client - 客户端
public class Client {
public static void main(String[] args) {
// 创建文本编辑器
TextEditor editor = new TextEditor();
TextEditorHistory history = new TextEditorHistory();
// 初始状态
System.out.println("------ 初始状态 ------");
editor.display();
history.save(editor.save());
// 输入文本
System.out.println("\n------ 输入文本 ------");
editor.type("Hello ");
editor.type("World!");
editor.display();
history.save(editor.save());
// 删除文本
System.out.println("\n------ 删除文本 ------");
editor.delete(6);
editor.display();
history.save(editor.save());
// 撤销
System.out.println("\n------ 撤销 ------");
if (history.canUndo()) {
editor.restore(history.undo());
editor.display();
}
// 再次撤销
System.out.println("\n------ 再次撤销 ------");
if (history.canUndo()) {
editor.restore(history.undo());
editor.display();
}
// 重做
System.out.println("\n------ 重做 ------");
if (history.canRedo()) {
editor.restore(history.redo());
editor.display();
}
// 再次重做
System.out.println("\n------ 再次重做 ------");
if (history.canRedo()) {
editor.restore(history.redo());
editor.display();
}
}
}
使用序列化实现备忘录模式
java
import java.io.*;
// Originator - 原发器类
public class SerializableOriginator implements Serializable {
private String state;
public SerializableOriginator(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
// 保存状态到字节数组
public byte[] saveStateToByteArray() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
oos.close();
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 从字节数组恢复状态
public void restoreStateFromByteArray(byte[] data) {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais);
SerializableOriginator originator = (SerializableOriginator) ois.readObject();
ois.close();
this.state = originator.getState();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// Caretaker - 负责人类
public class SerializableCaretaker {
private byte[] memento;
public void save(byte[] memento) {
this.memento = memento;
}
public byte[] getMemento() {
return memento;
}
}
// Client - 客户端
public class Client {
public static void main(String[] args) {
// 创建原发器
SerializableOriginator originator = new SerializableOriginator("初始状态");
System.out.println("初始状态: " + originator.getState());
// 创建负责人
SerializableCaretaker caretaker = new SerializableCaretaker();
// 保存状态
caretaker.save(originator.saveStateToByteArray());
// 修改状态
originator.setState("修改后的状态");
System.out.println("修改后的状态: " + originator.getState());
// 恢复状态
originator.restoreStateFromByteArray(caretaker.getMemento());
System.out.println("恢复后的状态: " + originator.getState());
}
}
优缺点
优点
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
- 提供了一种状态恢复的实现机制,使得系统可以更加灵活地处理状态变化
缺点
- 资源消耗过大,如果需要保存的状态过多,或者状态变化频繁,将会占用大量的内存资源
- 如果需要保存的状态非常复杂,可能会导致备忘录对象的创建和恢复成本较高
相关模式
- 命令模式:命令模式可以使用备忘录模式来实现可撤销的操作
- 原型模式:原型模式可以用来创建备忘录对象
- 状态模式:状态模式关注的是对象在不同状态下的行为,而备忘录模式关注的是对象状态的保存和恢复
实际应用
- 文本编辑器中的撤销/重做功能
- 数据库事务中的回滚操作
- 游戏中的存档和读档功能
- 版本控制系统中的版本管理
- IDE中的代码回滚功能
白箱备忘录与黑箱备忘录
白箱备忘录
白箱备忘录将备忘录类设计为原发器类的内部类,这样备忘录就可以访问原发器的所有状态。但这种方式会破坏封装性。
黑箱备忘录
黑箱备忘录将备忘录类设计为独立的类,只提供必要的接口来访问状态。这种方式保持了封装性,但可能需要更多的代码来实现状态的保存和恢复。
注意事项
- 备忘录模式中的备忘录对象应该是轻量级的,不应该包含过多的状态信息
- 备忘录模式中的负责人不应该直接操作备忘录对象,而应该通过原发器来操作
- 备忘录模式中的原发器应该负责创建和恢复备忘录对象
- 备忘录模式中的备忘录对象应该是不可变的,以防止状态被意外修改