看到“备忘录”这个名字的时候,我基本上不知道这个模式需要做的事情。而后又翻看了一下GoF的书,它的Intent是这个样子的:
Memento: Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.
在不破坏封装的前提下,获得对象的内部状态并外部化,用于以后的状态恢复行为。看这个解释,可以发现备忘录模式的要点有两方面:
- 外部化(externalize);
- 恢复(restore);
为了要恢复,所以得进行外部化。那么如何进行外部化也就直接关系到如何恢复。我们来举两个常见的场景,看看一般我们可以如何进行外部化和恢复。
撤销和重做
撤销(Undo)和重做(Redo)是我们在日常学习工作中经常用到的操作。为了要实现撤销和重做,我们要将每一个动作的状态保存起来(外部化),在适当的时候拿出来进行恢复。
我们首先定义两个接口,定义撤销和重做的相关方法。
public interface Undoable {
public void undo();
public boolean canUndo();
}
public interface Redoable {
public void redo();
public boolean canRedo();
}
然后我们写一个类来实现这两个接口。假设我们要写一个TextField,这个控件可以进行移动,默认的锚点是左上角的点,然后还可以往这个控件中增加字符。
package com.designpattern.memento;
import java.awt.*;
import java.util.ArrayDeque;
import java.util.Deque;
public class TextField implements Undoable, Redoable, Cloneable {
private Point currentPoint = new Point(0, 0);
private StringBuilder text = new StringBuilder();
private Deque<TextField> historyStack = new ArrayDeque<>();
private Deque<TextField> futureStack = new ArrayDeque<>();
//移动文本框
public void move(int x, int y) {
restoreState();
currentPoint.move(x, y);
}
//在文本框中输入文字
public void append(String str) {
restoreState();
text.append(str);
}
//保存状态
private void restoreState() {
historyStack.push((TextField) clone());
}
@Override
public void redo() {
if (canRedo() == false) {
System.out.println("No redo state found......");
return;
}
TextField instance = futureStack.pop();
this.currentPoint = new Point(instance.currentPoint);
this.text = new StringBuilder(instance.text.toString());
this.historyStack.push(instance);
}
@Override
public boolean canRedo() {
return futureStack.size() != 0;
}
@Override
public void undo() {
if (canUndo() == false) {
System.out.println("No undo state found......");
return;
}
this.futureStack.push((TextField) clone());
TextField instance = historyStack.pop();
this.currentPoint = new Point(instance.currentPoint);
this.text = new StringBuilder(instance.text.toString());
}
@Override
public boolean canUndo() {
return historyStack.size() != 0;
}
@Override
public Object clone() {
try {
super.clone();
TextField instance = new TextField();
instance.currentPoint = new Point(currentPoint);
instance.text = new StringBuilder(text.toString());
instance.historyStack = new ArrayDeque<>(historyStack);
instance.futureStack = new ArrayDeque<>(futureStack);
return instance;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
//打印输出
public void reportMyself() {
System.out.printf("Pos=(%d, %d) Text=\"%s\"%n", currentPoint.x, currentPoint.y, text.toString());
}
}
我们通过两个Queue来对状态进行储存,当调用undo或redo的时候,从相应的Queue中获得状态,然后覆盖给当前的对象。这样一来,我们就实现了备忘录模式。
注意其中用到了上篇文章中说到的原型模式(Prototype),由于我们必须进行深拷贝,所以实现了Cloneable接口,新建了一块内存保存对象的状态。
其实我们也可以不保存TextField对象,而是针对其中的关键数据,如Point的x/y坐标,StringBuilder对应的String进行保存,这样能够更节省内存。
SVN
其实我们日常使用的SVN,也可以算作是一种备忘录模式。它将不同版本的文件统一保存起来,当我们需要获得某一个时刻的文件版本时,可以从SVN库中拉取对应记录。
其实这有一点数据仓库的感觉,将每一版本的截面数据保存起来,用于后续的恢复和查看。而我们进行的文件保存与恢复,或者序列化与反序列化,如果按照备忘录模式的定义,也可以算作一种体现形式。
网友评论