說明:本文介紹行為型設計模式之一的備忘錄模式
定義
備忘錄模式(Memento Pattern)又叫作快照模式(Snapshot Pattern)或令牌模式(Token Pattern)指在不破壞封裝的前提下,捕獲一個對象的內部狀態,并在對象之外保存這個狀態。這樣以后就可將該對象恢復到原先保存的狀態,屬于行為型設計模式。
(引自《設計模式就該這樣學》P348,發現作者很喜歡使用”XX模式又叫作XX模式“這樣的表述,笑)
編輯器
假設開發一款編輯器軟件,如下,有載入文檔、追加內容、清空內容功能;
(文檔類,Doc)
/*** 文檔類*/
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)
/*** 編輯器類*/
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(">>>保存中");// todoSystem.out.println(">>>保存成功");}/*** 展示內容*/public void show() {System.out.println(">>>展示內容");System.out.println("文檔標題:" + doc.getTitle());System.out.println("文檔內容:" + doc.getBody());}
}
(客戶端使用,Client)
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)
/*** 歷史記錄類*/
public class History {private StringBuffer body;public History(StringBuffer body) {this.body = body;}public StringBuffer getBody() {return body;}
}
(其次,文檔對象中,增加保存歷史記錄,恢復歷史記錄的方法)
/*** 文檔類*/
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
,這里涉及到深拷貝、淺拷貝的問題,大家可以試試看有什么區別。
(最后,改造編輯器類,增加撤銷上一步操作的方法,并在每次修改操作后增加創建快照操作)
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(">>>保存中");// todoSystem.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)
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();}
}
可見撤銷操作成功恢復內容
多次撤銷,可實現逐步回退操作
// 撤回上一步myDoc.undo();myDoc.undo();myDoc.undo();myDoc.undo();
如下,非常nice。如果需要開發往后撤退的功能,也完全可以。
使用場景
在《設計模式就該這樣學》(P365)這本書中,提到狀態模式適用于以下場景:
(1)需要保存歷史快照的場景。
(2)希望在對象之外保存狀態,且除了自己,其他類對象無法訪問狀態保存的具體內容。
我覺得如果項目中,需要保存歷史記錄的場景,可以考慮使用備忘錄模式進行改造。
總結
本文介紹了行為型設計模式中的狀態模式,參考《設計模式就該這樣學》、《秒懂設計模式》兩書,編輯器場景是《秒懂設計模式》中的舉例。