一、什麼是備忘錄模式
備忘錄這個詞彙大家應該都不陌生,我就經常使用備忘錄來記錄一些比較重要的或者容易遺忘的資訊,與之相關的最常見的應用有許多,比如遊戲存檔,我們玩遊戲的時候肯定有存檔功能,旨在下一次登入遊戲時可以從上次退出的地方繼續遊戲,或者對復活點進行存檔,如果掛掉了則可以讀取復活點的存檔資訊重新開始。與之相類似的就是資料庫的事務回滾,或者重做日誌redo log等。
備忘錄模式(Memento),在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存著這個狀態。這樣以後就可將該物件恢復到原先儲存的狀態。UML結構圖如下:
其中,Originator是發起人,負責建立一個備忘錄Memento,用以記錄當前時刻它的內部狀態,並可使用備忘錄恢復內部狀態;Memento是備忘錄,負責儲存Originator物件的內部狀態,並可防止Originator以外的其他物件訪問備忘錄Memento;Caretaker是管理者,負責儲存好備忘錄的Memento,不能對備忘錄的內容進行操作或檢查。
1. 發起人角色
記錄當前時刻的內部狀態,並負責建立和恢復備忘錄資料,允許訪問返回到先前狀態所需的所有資料。
1 public class Originator { 2 3 private String state; 4 5 public String getState() { 6 return state; 7 } 8 9 public void setState(String state) { 10 this.state = state; 11 } 12 13 public Memento createMento() { 14 return (new Memento(state)); 15 } 16 17 public void setMemento(Memento memento) { 18 state = memento.getState(); 19 } 20 21 public void show() { 22 System.out.println("state = " + state); 23 } 24 25 }
2. 備忘錄角色
負責儲存Originator發起人物件的內部狀態,在需要的時候提供發起人需要的內部狀態。
1 public class Memento { 2 3 private String state; 4 5 public Memento(String state) { 6 this.state = state; 7 } 8 9 public String getState() { 10 return state; 11 } 12 13 }
3. 備忘錄管理員角色
對備忘錄進行管理、儲存和提供備忘錄,只能將備忘錄傳遞給其他角色。
1 public class Caretaker { 2 3 private Memento memento; 4 5 public Memento getMemento() { 6 return memento; 7 } 8 9 public void setMemento(Memento memento) { 10 this.memento = memento; 11 } 12 13 }
4. Client客戶端
下面編寫一小段程式碼測試一下,即先將狀態置為On,儲存後再將狀態置為Off,然後通過備忘錄管理員角色恢復初始狀態。
1 public class Client { 2 3 public static void main(String[] args) { 4 Originator originator = new Originator(); 5 originator.setState("On"); //Originator初始狀態 6 originator.show(); 7 8 Caretaker caretaker = new Caretaker(); 9 caretaker.setMemento(originator.createMento()); 10 11 originator.setState("Off"); //Originator狀態變為Off 12 originator.show(); 13 14 originator.setMemento(caretaker.getMemento()); //回覆初始狀態 15 originator.show(); 16 } 17 18 }
執行結果如下:
二、備忘錄模式的應用
1. 何時使用
- 需要記錄一個物件的內部狀態時,為了允許使用者取消不確定或者錯誤的操作,能夠恢復到原先的狀態
2. 方法
- 通過一個備忘錄類專門儲存物件狀態
3. 優點
- 給使用者提供了一種可以恢復狀態的機制,可以使用能夠比較方便地回到某個歷史的狀態
- 實現了資訊的封裝,使得使用者不需要關心狀態的儲存細節
4. 缺點
- 消耗資源
5. 使用場景
- 需要儲存和恢復資料的相關場景
- 提供一個可回滾的操作,如ctrl+z、瀏覽器回退按鈕、Backspace鍵等
- 需要監控的副本場景
6. 應用例項
- 遊戲存檔
- ctrl+z鍵、瀏覽器回退鍵等(撤銷/還原)
- 棋盤類遊戲的悔棋
- 資料庫事務的回滾
7. 注意事項
- 為了符合迪米特法則,需要有一個管理備忘錄的類
- 不要在頻繁建立備份的場景中使用備忘錄模式。為了節約記憶體,可使用原型模式+備忘錄模式
三、備忘錄模式的實現
下面以遊戲存檔為例,看一下如何用備忘錄模式實現。UML圖如下:
1. 遊戲角色
簡單記錄了遊戲角色的生命力、攻擊力、防禦力,通過saveState()方法來儲存當前狀態,通過recoveryState()方法來恢復角色狀態。
1 public class GameRole { 2 3 private int vit; //生命力 4 private int atk; //攻擊力 5 private int def; //防禦力 6 7 public int getVit() { 8 return vit; 9 } 10 public void setVit(int vit) { 11 this.vit = vit; 12 } 13 public int getAtk() { 14 return atk; 15 } 16 public void setAtk(int atk) { 17 this.atk = atk; 18 } 19 public int getDef() { 20 return def; 21 } 22 public void setDef(int def) { 23 this.def = def; 24 } 25 26 //狀態顯示 27 public void stateDisplay() { 28 System.out.println("角色當前狀態:"); 29 System.out.println("體力:" + this.vit); 30 System.out.println("攻擊力:" + this.atk); 31 System.out.println("防禦力: " + this.def); 32 System.out.println("-----------------"); 33 } 34 35 //獲得初始狀態 36 public void getInitState() { 37 this.vit = 100; 38 this.atk = 100; 39 this.def = 100; 40 } 41 42 //戰鬥後 43 public void fight() { 44 this.vit = 0; 45 this.atk = 0; 46 this.def = 0; 47 } 48 49 //儲存角色狀態 50 public RoleStateMemento saveState() { 51 return (new RoleStateMemento(vit, atk, def)); 52 } 53 54 //恢復角色狀態 55 public void recoveryState(RoleStateMemento memento) { 56 this.vit = memento.getVit(); 57 this.atk = memento.getAtk(); 58 this.def = memento.getDef(); 59 } 60 61 }
2. 角色狀態儲存箱
備忘錄類,用於儲存角色狀態。
1 public class RoleStateMemento { 2 3 private int vit; //生命力 4 private int atk; //攻擊力 5 private int def; //防禦力 6 7 public RoleStateMemento(int vit, int atk, int def) { 8 this.vit = vit; 9 this.atk = atk; 10 this.def = def; 11 } 12 13 public int getVit() { 14 return vit; 15 } 16 17 public void setVit(int vit) { 18 this.vit = vit; 19 } 20 21 public int getAtk() { 22 return atk; 23 } 24 25 public void setAtk(int atk) { 26 this.atk = atk; 27 } 28 29 public int getDef() { 30 return def; 31 } 32 33 public void setDef(int def) { 34 this.def = def; 35 } 36 37 }
3. 角色狀態管理者
備忘錄管理者。
1 public class RoleStateCaretaker { 2 3 private RoleStateMemento memento; 4 5 public RoleStateMemento getMemento() { 6 return memento; 7 } 8 9 public void setMemento(RoleStateMemento memento) { 10 this.memento = memento; 11 } 12 13 }
4. Client客戶端
下面編寫一個簡單的程式測試一下,編寫邏輯大致為打boss前存檔,打boss失敗了,讀檔。
1 public class Client { 2 3 public static void main(String[] args) { 4 //打boss前 5 GameRole gameRole = new GameRole(); 6 gameRole.getInitState(); 7 gameRole.stateDisplay(); 8 9 //儲存進度 10 RoleStateCaretaker caretaker = new RoleStateCaretaker(); 11 caretaker.setMemento(gameRole.saveState()); 12 13 //打boss失敗 14 gameRole.fight(); 15 gameRole.stateDisplay(); 16 17 //恢復狀態 18 gameRole.recoveryState(caretaker.getMemento()); 19 gameRole.stateDisplay(); 20 } 21 22 }
執行結果如下: