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