備忘錄模式在不破壞封裝性的前提下,捕獲並外部化對象的內部狀態,以便稍後恢復至該狀態。核心角色:
public final class EditorMemento {
private final String content;
private final int cursorPosition;
private final long timestamp;
public EditorMemento(String content, int cursorPosition) {
this.content = content;
this.cursorPosition = cursorPosition;
this.timestamp = System.currentTimeMillis();
}
// 僅提供給 Originator 的包訪問權限方法
String getContent() { return content; }
int getCursorPosition() { return cursorPosition; }
long getTimestamp() { return timestamp; }
}
public class TextEditor {
private String content = "";
private int cursorPosition = 0;
public void write(String text) {
content = content.substring(0, cursorPosition) + text + content.substring(cursorPosition);
cursorPosition += text.length();
}
public void delete(int length) {
if (cursorPosition >= length) {
content = content.substring(0, cursorPosition - length) + content.substring(cursorPosition);
cursorPosition -= length;
}
}
public void moveCursor(int pos) {
cursorPosition = Math.max(0, Math.min(pos, content.length()));
}
// 創建備忘錄
public EditorMemento save() {
return new EditorMemento(content, cursorPosition);
}
// 從備忘錄恢復
public void restore(EditorMemento memento) {
this.content = memento.getContent();
this.cursorPosition = memento.getCursorPosition();
}
@Override
public String toString() {
return String.format("內容: '%s' | 光標: %d", content, cursorPosition);
}
}
public class HistoryManager {
private final Deque undoStack = new ArrayDeque<>();
private final Deque redoStack = new ArrayDeque<>();
private static final int MAX_HISTORY = 100;
public void pushUndo(EditorMemento m) {
if (undoStack.size() >= MAX_HISTORY) undoStack.removeLast();
undoStack.push(m);
redoStack.clear(); // 新操作清空重做棧
}
public EditorMemento popUndo() { return undoStack.poll(); }
public EditorMemento popRedo() { return redoStack.poll(); }
public void pushRedo(EditorMemento m) { redoStack.push(m); }
public boolean canUndo() { return !undoStack.isEmpty(); }
public boolean canRedo() { return !redoStack.isEmpty(); }
}
public class MementoDemo {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
HistoryManager history = new HistoryManager();
// 初始狀態保存
history.pushUndo(editor.save());
System.out.println("初始: " + editor);
// 操作序列
editor.write("Hello ");
history.pushUndo(editor.save());
System.out.println("寫入: " + editor);
editor.write("World!");
history.pushUndo(editor.save());
System.out.println("寫入: " + editor);
editor.moveCursor(5);
editor.delete(6); // 刪除 "World!"
history.pushUndo(editor.save());
System.out.println("刪除: " + editor);
// 撤銷兩次
if (history.canUndo()) {
history.pushRedo(editor.save()); // 當前狀態入重做棧
editor.restore(history.popUndo());
System.out.println("撤銷1: " + editor);
}
if (history.canUndo()) {
history.pushRedo(editor.save());
editor.restore(history.popUndo());
System.out.println("撤銷2: " + editor);
}
// 重做一次
if (history.canRedo()) {
history.pushUndo(editor.save());
editor.restore(history.popRedo());
System.out.println("重做: " + editor);
}
}
}
初始: 內容: '' | 光標: 0 寫入: 內容: 'Hello ' | 光標: 6 寫入: 內容: 'Hello World!' | 光標: 12 刪除: 內容: 'Hello ' | 光標: 6 撤銷1: 內容: 'Hello World!' | 光標: 12 撤銷2: 內容: 'Hello ' | 光標: 6 重做: 內容: 'Hello World!' | 光標: 12
| 變體 | 特點 | 適用場景 |
|---|---|---|
| 完全備忘錄 | 備忘錄暴露所有狀態 getter,Originator 同包可見 | 狀態簡單、需完全恢復 |
| 增量備忘錄 | 只存儲變更差異 (如 Git diff),節省空間 | 狀態大、變更小、頻繁保存 |
| 原型模式結合 | Originator 實現 Cloneable,直接 clone 作為備忘錄 | 狀態對象支持深拷貝、結構穩定 |
| 序列化備忘錄 | 將狀態序列化為字節數組/JSON 存儲 | 跨進程/持久化/分布式恢復 |