说明:本文介绍行为型设计模式之一的备忘录模式
定义
备忘录模式(Memento Pattern)又叫作快照模式(Snapshot Pattern)或令牌模式(Token Pattern)指在不破坏封装的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态,属于行为型设计模式。
(引自《设计模式就该这样学》P348,发现作者很喜欢使用"XX模式又叫作XX模式"这样的表述,苦笑)
编辑器
假设开发一款编辑器软件,如下,有载入文档、追加内容、清空内容功能;
(文档类,Doc)
java
/**
* 文档类
*/
public class Doc {
/**
* 文档标题
*/
private String title;
/**
* 文档内容
*/
private StringBuffer body;
public Doc(String title) {
this.title = title;
this.body = new StringBuffer();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public StringBuffer getBody() {
return body;
}
public void setBody(StringBuffer body) {
this.body = body;
}
}
(编辑器,Editor)
java
/**
* 编辑器类
*/
public class Editor {
private Doc doc;
/**
* 载入文档
*/
public Editor(Doc doc) {
System.out.println(">>>载入文档");
this.doc = doc;
show();
}
/**
* 追加内容
*/
public void append(String content){
System.out.println(">>>追加内容");
doc.getBody().append(content);
show();
}
/**
* 清空内容
*/
public void clear() {
System.out.println(">>>清空文档");
doc.getBody().setLength(0);
show();
}
/**
* 保存内容
*/
public void save() {
System.out.println(">>>保存中");
// todo
System.out.println(">>>保存成功");
}
/**
* 展示内容
*/
public void show() {
System.out.println(">>>展示内容");
System.out.println("文档标题:" + doc.getTitle());
System.out.println("文档内容:" + doc.getBody());
}
}
(客户端使用,Client)
java
public class Client {
public static void main(String[] args) {
// 打开编辑器,开始写作
Editor myDoc = new Editor(new Doc("《论程序员的自我修养》"));
// 巴拉巴拉,写作中
myDoc.append("\n第一章:程序员必备知识");
myDoc.append("\n第二章:论艺术涵养对程序员编码的影响");
// 看看,嗯,写得很好
myDoc.show();
// 保存
myDoc.save();
// 继续
myDoc.append("\n第三章:论打游戏技术与程序员技术之间的关联");
// 误操作。。。
myDoc.clear();
}
}
可见,如果误操作导致文档内容被清空,九分甚至十分的糟糕

针对以上功能,利用备忘录模式进行改造,如下:
(首先,创建一个历史记录对象,保存文档内容,History)
java
/**
* 历史记录类
*/
public class History {
private StringBuffer body;
public History(StringBuffer body) {
this.body = body;
}
public StringBuffer getBody() {
return body;
}
}
(其次,文档对象中,增加保存历史记录,恢复历史记录的方法)
java
/**
* 文档类
*/
public class Doc {
/**
* 文档标题
*/
private String title;
/**
* 文档内容
*/
private StringBuffer body;
public Doc(String title) {
this.title = title;
this.body = new StringBuffer();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public StringBuffer getBody() {
return body;
}
public void setBody(StringBuffer body) {
this.body = body;
}
/**
* 创建历史记录
*/
public History createHistory() {
return new History(new StringBuffer(body));
}
/**
* 恢复历史记录
*/
public void restoreHistory(History history) {
body = history.getBody();
}
}
注意创建历史记录方法里,使用了new StringBuffer(body)
,而不是直接传递body
,这里涉及到深拷贝、浅拷贝的问题,大家可以试试看有什么区别。
(最后,改造编辑器类,增加撤销上一步操作的方法,并在每次修改操作后增加创建快照操作)
java
import java.util.List;
/**
* 编辑器类
*/
public class Editor {
/**
* 文档对象
*/
private Doc doc;
/**
* 历史记录集合
*/
private List<History> historyList;
/**
* 历史记录版本号
* 初始值为-1
*/
private int historyVersion = -1;
/**
* 载入文档
*/
public Editor(Doc doc) {
System.out.println(">>>载入文档");
this.doc = doc;
historyList = new java.util.ArrayList<>();
backup();
show();
}
/**
* 追加内容
*/
public void append(String content){
System.out.println(">>>追加内容");
doc.getBody().append(content);
backup();
show();
}
/**
* 清空内容
*/
public void clear() {
System.out.println(">>>清空文档");
doc.getBody().setLength(0);
backup();
show();
}
/**
* 保存内容
*/
public void save() {
System.out.println(">>>保存中");
// todo
System.out.println(">>>保存成功");
}
/**
* 展示内容
*/
public void show() {
System.out.println(">>>展示内容");
System.out.println("文档标题:" + doc.getTitle());
System.out.println("文档内容:" + doc.getBody());
}
/**
* 保存历史记录
* 或者说创建快照
*/
private void backup() {
historyList.add(doc.createHistory());
historyVersion++;
}
/**
* 撤回上一步
*/
public void undo() {
System.out.println(">>>撤销操作");
if (historyVersion == 0) {
return;
}
historyVersion--;
History history = historyList.get(historyVersion);
doc.restoreHistory(history);
show();
}
}
(客户端使用,Client)
java
public class Client {
public static void main(String[] args) {
// 打开编辑器,开始写作
Editor myDoc = new Editor(new Doc("《论程序员的自我修养》"));
// 巴拉巴拉,写作中
myDoc.append("\n第一章:程序员必备知识");
myDoc.append("\n第二章:论艺术涵养对程序员编码的影响");
// 看看,嗯,写得很好
myDoc.show();
// 保存
myDoc.save();
// 继续
myDoc.append("\n第三章:论打游戏技术与程序员技术之间的关联");
// 误操作。。。
myDoc.clear();
// 撤回上一步
myDoc.undo();
}
}
可见撤销操作成功恢复内容

多次撤销,可实现逐步回退操作
java
// 撤回上一步
myDoc.undo();
myDoc.undo();
myDoc.undo();
myDoc.undo();
如下,非常nice。如果需要开发往后撤退的功能,也完全可以。

使用场景
在《设计模式就该这样学》(P365)这本书中,提到状态模式适用于以下场景:
(1)需要保存历史快照的场景。
(2)希望在对象之外保存状态,且除了自己,其他类对象无法访问状态保存的具体内容。
我觉得如果项目中,需要保存历史记录的场景,可以考虑使用备忘录模式进行改造。
总结
本文介绍了行为型设计模式中的状态模式,参考《设计模式就该这样学》、《秒懂设计模式》两书,编辑器场景是《秒懂设计模式》中的举例。