看到“備忘錄”這個名字的時候眉抬,我基本上不知道這個模式需要做的事情。而后又翻看了一下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.
在不破壞封裝的前提下蜀变,獲得對象的內部狀態(tài)并外部化,用于以后的狀態(tài)恢復行為介评】獗保看這個解釋爬舰,可以發(fā)現(xiàn)備忘錄模式的要點有兩方面:
- 外部化(externalize);
- 恢復(restore)寒瓦;
為了要恢復情屹,所以得進行外部化。那么如何進行外部化也就直接關系到如何恢復杂腰。我們來舉兩個常見的場景垃你,看看一般我們可以如何進行外部化和恢復。
撤銷和重做
撤銷(Undo)和重做(Redo)是我們在日常學習工作中經常用到的操作喂很。為了要實現(xiàn)撤銷和重做惜颇,我們要將每一個動作的狀態(tài)保存起來(外部化),在適當?shù)臅r候拿出來進行恢復恤筛。
我們首先定義兩個接口官还,定義撤銷和重做的相關方法芹橡。
public interface Undoable {
public void undo();
public boolean canUndo();
}
public interface Redoable {
public void redo();
public boolean canRedo();
}
然后我們寫一個類來實現(xiàn)這兩個接口毒坛。假設我們要寫一個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);
}
//保存狀態(tài)
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來對狀態(tài)進行儲存腿箩,當調用undo或redo的時候豪直,從相應的Queue中獲得狀態(tài),然后覆蓋給當前的對象珠移。這樣一來弓乙,我們就實現(xiàn)了備忘錄模式。
注意其中用到了上篇文章中說到的原型模式(Prototype)钧惧,由于我們必須進行深拷貝暇韧,所以實現(xiàn)了Cloneable接口,新建了一塊內存保存對象的狀態(tài)浓瞪。
其實我們也可以不保存TextField對象懈玻,而是針對其中的關鍵數(shù)據,如Point的x/y坐標乾颁,StringBuilder對應的String進行保存涂乌,這樣能夠更節(jié)省內存。
SVN
其實我們日常使用的SVN英岭,也可以算作是一種備忘錄模式湾盒。它將不同版本的文件統(tǒng)一保存起來,當我們需要獲得某一個時刻的文件版本時诅妹,可以從SVN庫中拉取對應記錄罚勾。
其實這有一點數(shù)據倉庫的感覺,將每一版本的截面數(shù)據保存起來,用于后續(xù)的恢復和查看荧库。而我們進行的文件保存與恢復堰塌,或者序列化與反序列化,如果按照備忘錄模式的定義分衫,也可以算作一種體現(xiàn)形式场刑。