簡說設計模式——備忘錄模式


一、什么是備忘錄模式

  備忘錄這個詞匯大家應該都不陌生,我就經常使用備忘錄來記錄一些比較重要的或者容易遺忘的信息,與之相關的最常見的應用有許多,比如游戲存檔,我們玩游戲的時候肯定有存檔功能,旨在下一次登錄游戲時可以從上次退出的地方繼續游戲,或者對復活點進行存檔,如果掛掉了則可以讀取復活點的存檔信息重新開始。與之相類似的就是數據庫的事務回滾,或者重做日志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 }

  運行結果如下:

  

 

  源碼地址:https://gitee.com/adamjiangwh/GoF  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM