本文的主要內容:
- 介紹備忘錄模式
- 示例
- 備忘錄模式總結
更多內容請訪問我的個人部落格:laijianfeng.org
關注【小旋鋒】微信公眾號,及時接收博文推送
備忘錄模式
備忘錄模式經常可以遇到,譬如下面這些場景:
-
瀏覽器回退:瀏覽器一般有瀏覽記錄,當我們在一個網頁上點選幾次連結之後,可在左上角點選左箭頭回退到上一次的頁面,然後也可以點選右箭頭重新回到當前頁面
-
資料庫備份與還原:一般的資料庫都支援備份與還原操作,備份即將當前已有的資料或者記錄保留,還原即將已經保留的資料恢復到對應的表中
-
編輯器撤銷與重做:在編輯器上編輯文字,寫錯時可以按快捷鍵
Ctrl + z
撤銷,撤銷後可以按Ctrl + y
重做 -
虛擬機器生成快照與恢復:虛擬機器可以生成一個快照,當虛擬機器發生錯誤時可以恢復到快照的樣子
-
Git版本管理:Git是最常見的版本管理軟體,每提交一個新版本,實際上Git就會把它們自動串成一條時間線,每個版本都有一個版本號,使用
git reset --hard 版本號
即可回到指定的版本,讓程式碼時空穿梭回到過去某個歷史時刻 -
下棋遊戲悔棋:在下棋遊戲中,有時下快了可以悔棋,回退到上一步重新下
備忘錄模式(Memento Pattern):在不破壞封裝的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態,這樣可以在以後將物件恢復到原先儲存的狀態。它是一種物件行為型模式,其別名為Token。
角色
Originator(原發器):它是一個普通類,可以建立一個備忘錄,並儲存它的當前內部狀態,也可以使用備忘錄來恢復其內部狀態,一般將需要儲存內部狀態的類設計為原發器。
Memento(備忘錄):儲存原發器的內部狀態,根據原發器來決定儲存哪些內部狀態。備忘錄的設計一般可以參考原發器的設計,根據實際需要確定備忘錄類中的屬性。需要注意的是,除了原發器本身與負責人類之外,備忘錄物件不能直接供其他類使用,原發器的設計在不同的程式語言中實現機制會有所不同。
Caretaker(負責人):負責人又稱為管理者,它負責儲存備忘錄,但是不能對備忘錄的內容進行操作或檢查。在負責人類中可以儲存一個或多個備忘錄物件,它只負責儲存物件,而不能修改物件,也無須知道物件的實現細節。
備忘錄模式的核心是備忘錄類以及用於管理備忘錄的負責人類的設計。
示例
下棋例子,可以下棋,悔棋,撤銷悔棋等
棋子類 Chessman
,原發器角色
@Data
@AllArgsConstructor
class Chessman {
private String label;
private int x;
private int y;
//儲存狀態
public ChessmanMemento save() {
return new ChessmanMemento(this.label, this.x, this.y);
}
//恢復狀態
public void restore(ChessmanMemento memento) {
this.label = memento.getLabel();
this.x = memento.getX();
this.y = memento.getY();
}
public void show() {
System.out.println(String.format("棋子<%s>:當前位置為:<%d, %d>", this.getLabel(), this.getX(), this.getY()));
}
}
複製程式碼
備忘錄角色 ChessmanMemento
@Data
@AllArgsConstructor
class ChessmanMemento {
private String label;
private int x;
private int y;
}
複製程式碼
負責人角色 MementoCaretaker
class MementoCaretaker {
//定義一個集合來儲存備忘錄
private ArrayList mementolist = new ArrayList();
public ChessmanMemento getMemento(int i) {
return (ChessmanMemento) mementolist.get(i);
}
public void addMemento(ChessmanMemento memento) {
mementolist.add(memento);
}
}
複製程式碼
棋子客戶端,維護了一個 MementoCaretaker
物件
class Client {
private static int index = -1;
private static MementoCaretaker mc = new MementoCaretaker();
public static void main(String args[]) {
Chessman chess = new Chessman("車", 1, 1);
play(chess);
chess.setY(4);
play(chess);
chess.setX(5);
play(chess);
undo(chess, index);
undo(chess, index);
redo(chess, index);
redo(chess, index);
}
//下棋,同時儲存備忘錄
public static void play(Chessman chess) {
mc.addMemento(chess.save());
index++;
chess.show();
}
//悔棋,撤銷到上一個備忘錄
public static void undo(Chessman chess, int i) {
System.out.println("******悔棋******");
index--;
chess.restore(mc.getMemento(i - 1));
chess.show();
}
//撤銷悔棋,恢復到下一個備忘錄
public static void redo(Chessman chess, int i) {
System.out.println("******撤銷悔棋******");
index++;
chess.restore(mc.getMemento(i + 1));
chess.show();
}
}
複製程式碼
輸出如下,悔棋成功,撤銷悔棋成功
棋子<車>:當前位置為:<1, 1>
棋子<車>:當前位置為:<1, 4>
棋子<車>:當前位置為:<5, 4>
******悔棋******
棋子<車>:當前位置為:<1, 4>
******悔棋******
棋子<車>:當前位置為:<1, 1>
******撤銷悔棋******
棋子<車>:當前位置為:<1, 4>
******撤銷悔棋******
棋子<車>:當前位置為:<5, 4>
複製程式碼
類圖如下
備忘錄模式總結
備忘錄模式的主要優點如下:
-
它提供了一種狀態恢復的實現機制,使得使用者可以方便地回到一個特定的歷史步驟,當新的狀態無效或者存在問題時,可以使用暫時儲存起來的備忘錄將狀態復原。
-
備忘錄實現了對資訊的封裝,一個備忘錄物件是一種原發器物件狀態的表示,不會被其他程式碼所改動。備忘錄儲存了原發器的狀態,採用列表、堆疊等集合來儲存備忘錄物件可以實現多次撤銷操作。
備忘錄模式的主要缺點如下:
- 資源消耗過大,如果需要儲存的原發器類的成員變數太多,就不可避免需要佔用大量的儲存空間,每儲存一次物件的狀態都需要消耗一定的系統資源。
適用場景:
-
儲存一個物件在某一個時刻的全部狀態或部分狀態,這樣以後需要時它能夠恢復到先前的狀態,實現撤銷操作。
-
防止外界物件破壞一個物件歷史狀態的封裝性,避免將物件歷史狀態的實現細節暴露給外界物件。
由於JDK、Spring、Mybatis中很少有備忘錄模式,也許 Spring webflow 中的 StateManageableMessageContext 介面算一個,但是真的很少見,所以這裡不做典型應用原始碼分析
推薦閱讀
設計模式 | 簡單工廠模式及典型應用
設計模式 | 工廠方法模式及典型應用
設計模式 | 抽象工廠模式及典型應用
設計模式 | 建造者模式及典型應用
設計模式 | 原型模式及典型應用
設計模式 | 外觀模式及典型應用
設計模式 | 裝飾者模式及典型應用
設計模式 | 介面卡模式及典型應用
設計模式 | 享元模式及典型應用
設計模式 | 組合模式及典型應用
設計模式 | 模板方法模式及典型應用
設計模式 | 迭代器模式及典型應用
設計模式 | 策略模式及典型應用
設計模式 | 觀察者模式及典型應用