備忘錄模式(Memento Pattern) – 設計模式之行為模式
備忘錄模式(Memento Pattern) – 設計模式之行為模式:
目錄
備忘錄模式(Memento Pattern)
定義: Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.
在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態。這樣以後就可以將該物件恢復到原先儲存的狀態。
類圖
備忘錄模式通用類圖:
例子:
過程:
在玩遊戲的時候,經常會進行存檔,尤其難度高的,幾乎是過一關存檔一次。存檔,需要建立yige-備份的物件,跟遊戲物件相類似。有存檔和恢復的操作
類圖:
程式碼:
遊戲:Game
public class Game { private int nums;// 關卡 private String content;// 遊戲的內容 public Game(int nums, String content) { this.nums = nums; this.content = content; } public int getNums() { return nums; } public void setNums(int nums) { this.nums = nums; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public GameHistory createHistory() { return new GameHistory(nums, content);//建立歷史記錄 } public void restoreHistory(GameHistory gameHistory){ this.nums = gameHistory.getNums(); this.content = gameHistory.getContent(); } }
遊戲備份:GameHistory
public class GameHistory { private int nums; // 用於備用關卡 private String content;//用於備忘遊戲內容 public GameHistory(int nums, String content) { this.nums = nums; this.content = content; } public int getNums() { return nums; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
玩家:Player
public class Player {
private Game game;
private List<GameHistory> gameHistoryRecords;// 歷史記錄列表
public Player(Game game) {
System.out.println("<<<開啟遊戲" + game.getNums());
this.game = game;
gameHistoryRecords = new ArrayList<>();// 初始化歷史記錄
}
public void savePoint(int num, String msg) {
System.out.println("打到第"+num+"關了,及時儲存下");
game.setNums(num);
game.setContent(game.getContent() + msg);
backup();//操作完成後儲存歷史記錄
show();
}
private void backup() {
gameHistoryRecords.add(game.createHistory());
}
private void show() {
System.out.println(game.getNums() + " 內容 "+ game.getContent());
}
public void undo(int num) {// 跳到第x關,重新開始開
System.out.println(">>>恢復操作");
if (num < 0) {
return;
}
Map<Integer, GameHistory> historyMap = toMapInfo();
GameHistory gameHistory = historyMap.get(num);
game.restoreHistory(gameHistory);//取出歷史記錄並恢復至文件
show();
}
// (x, y) -> y 取最近儲存的,
private Map<Integer, GameHistory> toMapInfo(){
return ListUtils.emptyIfNull(gameHistoryRecords).stream()
.collect(Collectors.toMap(GameHistory::getNums, f -> f, (x, y) -> y));
}
}
測試:
public class PlayerTest {
public static void main(String[] args) {
Player player = new Player(new Game(3,"打到第三關,武力值為500"));
player.savePoint(5, "打到第五關了,武力值五600");
player.savePoint(9, "打到第九關了,武力值五1100");
player.savePoint(10, "打到第十關了,武力值五1155");
player.savePoint(17, "打到第十七關了,武力值五1500");
System.out.println("=========");
player.undo(9);
player.savePoint(10, "打到第十關了,武力值五1300");
player.savePoint(11, "打到第十一關了,武力值五1310");
System.out.println("=========");
player.undo(10);
player.savePoint(12, "打到第十二關了,武力值五1400");
}
}
結果:
<<<開啟遊戲3
打到第5關了,及時儲存下
5 內容 打到第三關,武力值為500打到第五關了,武力值五600
打到第9關了,及時儲存下
9 內容 打到第三關,武力值為500打到第五關了,武力值五600打到第九關了,武力值五1100
打到第10關了,及時儲存下
10 內容 打到第三關,武力值為500打到第五關了,武力值五600打到第九關了,武力值五1100打到第十關了,武力值五1155
打到第17關了,及時儲存下
17 內容 打到第三關,武力值為500打到第五關了,武力值五600打到第九關了,武力值五1100打到第十關了,武力值五1155打到第十七關了,武力值五1500
=========
>>>恢復操作
9 內容 打到第三關,武力值為500打到第五關了,武力值五600打到第九關了,武力值五1100
打到第10關了,及時儲存下
10 內容 打到第三關,武力值為500打到第五關了,武力值五600打到第九關了,武力值五1100打到第十關了,武力值五1300
打到第11關了,及時儲存下
11 內容 打到第三關,武力值為500打到第五關了,武力值五600打到第九關了,武力值五1100打到第十關了,武力值五1300打到第十一關了,武力值五1310
=========
>>>恢復操作
10 內容 打到第三關,武力值為500打到第五關了,武力值五600打到第九關了,武力值五1100打到第十關了,武力值五1300
打到第12關了,及時儲存下
12 內容 打到第三關,武力值為500打到第五關了,武力值五600打到第九關了,武力值五1100打到第十關了,武力值五1300打到第十二關了,武力值五1400
總結:
優點:
1、給使用者提供了一種可以恢復狀態的機制,可以使使用者能夠比較方便地回到某個歷史的狀態。
2、實現了資訊的封裝,使得使用者不需要關心狀態的儲存細節。
缺點:消耗資源。如果類的成員變數過多,勢必會佔用比較大的資源,而且每一次儲存都會消耗一定的記憶體。為了節約記憶體,可使用原型模式+備忘錄模式。
使用場景
1, 需要儲存和恢復資料的相關狀態場景。
2, 提供一個可回滾(rollback)的操作;比如Word中的CTRL+Z組合鍵,IE瀏覽器中的後退按鈕,檔案管理器上的backspace鍵等。
3, 需要監控的副本場景中。例如要監控一個物件的屬性,但是監控又不應該作為系統的主業務來呼叫,它只是邊緣應用,即使出現監控不準、錯誤報警也影響不大,因此一般的做法是備份一個主執行緒中的物件,然後由分析程式來分析。
4,資料庫連線的事務管理就是用的備忘錄模式,想想看,如果你要實現一個JDBC驅動,你怎麼來實現事務?還不是用備忘錄模式嘛!
注意事項
1, 備忘錄的生命期
備忘錄創建出來就要在“最近”的程式碼中使用,要主動管理它的生命週期,建立就要使用,不使用就要立刻刪除其引用,等待垃圾回收器對它的回收處理。
2, 備忘錄的效能
不要在頻繁建立備份的場景中使用備忘錄模式(比如一個for迴圈中),原因有二:一是控制不了備忘錄建立的物件數量;二是大物件的建立是要消耗資源的,系統的效能需要考慮。因此,如果出現這樣的程式碼,設計者就應該好好想想怎麼修改架構了。