本文的主要內容:
- 介紹備忘錄模式
- 示例
- 備忘錄模式總結
備忘錄模式
備忘錄模式經常可以遇到,譬如下面這些場景:
瀏覽器回退:瀏覽器一般有瀏覽記錄,當我們在一個網頁上點擊幾次鏈接之后,可在左上角點擊左箭頭回退到上一次的頁面,然后也可以點擊右箭頭重新回到當前頁面
數據庫備份與還原:一般的數據庫都支持備份與還原操作,備份即將當前已有的數據或者記錄保留,還原即將已經保留的數據恢復到對應的表中
編輯器撤銷與重做:在編輯器上編輯文字,寫錯時可以按快捷鍵
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 接口算一個,但是真的很少見,所以這里不做典型應用源碼分析
后記
歡迎評論、轉發、分享,您的支持是我最大的動力
更多內容可訪問我的個人博客:http://laijianfeng.org
關注【小旋鋒】微信公眾號,及時接收博文推送