概念
备忘录模式(Memento Pattern)是一种行为型设计模式,主要用于保存对象的内部状态,以便在需要时恢复到先前的状态。这种模式有助于实现撤销、恢复或回滚操作,同时保持对象封装性。
组成角色
- 发起人(Originator):负责创建一个备忘录,用于存储当前对象的内部状态,并在需要时恢复到先前的状态。
- 备忘录(Memento):存储发起人对象的内部状态。备忘录应该只能被发起人访问和修改。
- 管理者(Caretaker):负责存储备忘录。管理者不应修改或直接访问备忘录的内容。
示例代码
在此示例中,BankAccount(Originator)类表示一个简单的银行账户,支持存款和取款操作。每次执行操作时,我们都将当前余额保存到一个BankAccountMemento(Memento)对象中,并将其添加到BankAccountCaretaker对象的列表中。当需要执行撤销操作时,我们可以通过BankAccountCaretaker对象获取相应的BankAccountMemento对象,并将BankAccount的余额恢复到先前的状态。
package design.pattern.Memento;
import java.util.Stack;
// 定义一个可撤销操作的接口
interface UndoableOperation {
void deposit(double amount);
void withdraw(double amount);
void undo();
void redo();
double getBalance();
}
// Memento 类,存储银行账户的余额
class BankAccountMemento {
private double balance;
public BankAccountMemento(double balance) {
this.balance = balance;
}
public double getBalance() {
return balance;
}
}
// Originator 类,实现了 UndoableOperation 接口
class BankAccount implements UndoableOperation {
private double balance;
// 用来存储撤销操作的栈
private Stack<BankAccountMemento> undoStack = new Stack<>();
// 用来存储重做操作的栈
private Stack<BankAccountMemento> redoStack = new Stack<>();
public BankAccount(double balance) {
this.balance = balance;
undoStack.push(saveToMemento());
}
@Override
public void deposit(double amount) {
redoStack.clear();
balance += amount;
undoStack.push(saveToMemento());
}
@Override
public void withdraw(double amount) {
redoStack.clear();
balance -= amount;
undoStack.push(saveToMemento());
}
@Override
public double getBalance() {
return balance;
}
private BankAccountMemento saveToMemento() {
return new BankAccountMemento(balance);
}
private void restoreFromMemento(BankAccountMemento memento) {
balance = memento.getBalance();
}
@Override
public void undo() {
if (!undoStack.isEmpty()) {
redoStack.push(undoStack.pop());
if (!undoStack.isEmpty()) {
restoreFromMemento(undoStack.peek());
}
}
}
@Override
public void redo() {
if (!redoStack.isEmpty()) {
BankAccountMemento memento = redoStack.pop();
restoreFromMemento(memento);
undoStack.push(memento);
}
}
}
//扮演Caretaker的角色
public class MementoPatternDemo {
public static void main(String[] args) {
// 创建一个实现了 UndoableOperation 接口的 BankAccount 对象,开始操作时金额为1000
UndoableOperation bankAccount = new BankAccount(1000.0);
// 执行存款操作
bankAccount.deposit(500);
System.out.println("Current balance: " + ((BankAccount) bankAccount).getBalance());
// 执行取款操作
bankAccount.withdraw(200);
System.out.println("Current balance: " + ((BankAccount) bankAccount).getBalance());
// 执行撤销操作
bankAccount.undo();
System.out.println("Undo last change: " + ((BankAccount) bankAccount).getBalance());
// 再次执行撤销操作
bankAccount.undo();
System.out.println("Undo two changes: " + ((BankAccount) bankAccount).getBalance());
// 执行重做操作
bankAccount.redo();
System.out.println("Redo last change: " + ((BankAccount) bankAccount).getBalance());
//* (存500) (取200) (撤销一次操作) (撤销两次操作) (执行重做操作) *//
//*金额变化:1000 ------> 1500 ------> 1300 ------------> 1500 ------------> 1000 ------------> 1500 *//
}
}
框架中的运用
Swing库中的javax.swing.undo
包,它提供了一种在Swing应用程序中实现撤销和重做功能的通用框架。
首先,我们需要创建一个简单的Swing应用程序,包含一个JTextArea
组件,并添加撤销和重做按钮。
import javax.swing.*;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import java.awt.*;
public class SwingMementoDemo {
public static void main(String[] args) {
//在这个示例中,我们使用了Swing库中的UndoManager类,它实际上是一个Caretaker角色的实现。UndoManager类维护一个存储UndoableEdit对象(表示可撤销的编辑操作)的栈。UndoableEdit是一个接口,表示一个可撤销和重做的编辑操作,它的实现类充当了Memento角色。
JFrame frame = new JFrame("Swing Memento Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
JTextArea textArea = new JTextArea();
JScrollPane scrollPane = new JScrollPane(textArea);
frame.add(scrollPane, BorderLayout.CENTER);
// Set up UndoManager
UndoManager undoManager = new UndoManager();
textArea.getDocument().addUndoableEditListener(undoManager);
// Set up buttons
JPanel buttonPanel = new JPanel();
JButton undoButton = new JButton("Undo");
JButton redoButton = new JButton("Redo");
buttonPanel.add(undoButton);
buttonPanel.add(redoButton);
frame.add(buttonPanel, BorderLayout.NORTH);
// Set up button actions
undoButton.addActionListener(e -> {
try {
if (undoManager.canUndo()) {
undoManager.undo();
}
} catch (CannotUndoException ex) {
ex.printStackTrace();
}
});
redoButton.addActionListener(e -> {
try {
if (undoManager.canRedo()) {
undoManager.redo();
}
} catch (CannotRedoException ex) {
ex.printStackTrace();
}
});
frame.setVisible(true);
}
}
运行代码
可以在窗口中进行撤销和重做操作
适用场景
- 需要实现撤销(Undo)和重做(Redo)操作:在文本编辑器、图像编辑器或数据库事务中,用户可能希望撤销或重做先前的操作。使用备忘录模式,可以保存对象的状态,然后在需要时恢复到特定的状态。
- 需要备份和恢复状态:在某些情况下,可能需要在某个时间点创建对象状态的快照,并在将来的某个时刻将对象恢复到该状态。例如,在游戏中,玩家可能希望保存游戏进度并在以后继续游戏。
- 需要限制对象状态的直接访问:如果希望对外部对象隐藏某个对象的内部状态,可以使用备忘录模式。这样,外部对象无法直接访问或修改对象的内部状态,只能通过
Originator
提供的接口与其状态进行交互。 - 需要在不违反封装原则的前提下,暂存对象的内部状态:备忘录模式允许对象在不暴露其实现细节的情况下,保存和恢复其内部状态。这符合封装原则,有助于保持代码的整洁和易于维护。