Java設計模式之java備忘錄模式詳解
引言
備忘錄模式經(jīng)??梢杂龅?,譬如下面這些場景:
- 瀏覽器回退:瀏覽器一般有瀏覽記錄,當我們在一個網(wǎng)頁上點擊幾次鏈接之后,可在左上角點擊左箭頭回退到上一次的頁面,然后也可以點擊右箭頭重新回到當前頁面
- 數(shù)據(jù)庫備份與還原:一般的數(shù)據(jù)庫都支持備份與還原操作,備份即將當前已有的數(shù)據(jù)或者記錄保留,還原即將已經(jīng)保留的數(shù)據(jù)恢復到對應的表中
- 編輯器撤銷與重做:在編輯器上編輯文字,寫錯時可以按快捷鍵 Ctrl + z 撤銷,撤銷后可以按 Ctrl + y 重做
- 虛擬機生成快照與恢復:虛擬機可以生成一個快照,當虛擬機發(fā)生錯誤時可以恢復到快照的樣子
- Git版本管理:Git是最常見的版本管理軟件,每提交一個新版本,實際上Git就會把它們自動串成一條時間線,每個版本都有一個版本號,使用 git reset --hard 版本號 即可回到指定的版本,讓代碼時空穿梭回到過去某個歷史時刻
- 棋牌游戲悔棋:在棋牌游戲中,有時下快了可以悔棋,回退到上一步重新下
備忘錄模式(Memento Pattern)
在不破壞封裝的前提下,捕獲一個對象的內部狀態(tài),并在該對象之外保存這個狀態(tài),這樣可以在以后將對象恢復到原先保存的狀態(tài)。它是一種對象行為型模式,其別名為Token。
角色
- Originator(原發(fā)器):它是一個普通類,可以創(chuàng)建一個備忘錄,并存儲它的當前內部狀態(tài),也可以使用備忘錄來恢復其內部狀態(tài),一般將需要保存內部狀態(tài)的類設計為原發(fā)器,需要被數(shù)據(jù)備份的對象
- Memento(備忘錄):存儲原發(fā)器的內部狀態(tài),根據(jù)原發(fā)器來決定保存哪些內部狀態(tài)。備忘錄的設計一般可以參考原發(fā)器的設計,根據(jù)實際需要確定備忘錄類中的屬性。需要注意的是,除了原發(fā)器本身與負責人類之外,備忘錄對象不能直接供其他類使用,原發(fā)器的設計在不同的編程語言中實現(xiàn)機制會有所不同。用來保存?zhèn)浞輸?shù)據(jù)的對象
- Caretaker(負責人):負責人又稱為管理者,它負責保存?zhèn)渫?,但是不能對備忘錄的內容進行操作或檢查。在負責人類中可以存儲一個或多個備忘錄對象,它只負責存儲對象,而不能修改對象,也無須知道對象的實現(xiàn)細節(jié)。備份的數(shù)據(jù)會有多份,因此需要有一個類來管理這些備份
備忘錄模式的核心是備忘錄類以及用于管理備忘錄的負責人類的設計。
說明:如果希望保存多個originator對象的不同時間的狀態(tài),也可以,只需要 HashMap <String, 集合>
為什么會出現(xiàn)守護者對象(負責人)?
舉個例子說明,下棋軟件要提供“悔棋”功能,用戶走錯棋或操作失誤后可恢復到前一個步驟。悔棋可能回到上一步,也有可能回到上上次的狀態(tài)…因此需要記錄多次的狀態(tài)


在設計備忘錄類時需要考慮其封裝性,除了Originator類,不允許其他類來調用備忘錄類Memento的構造函數(shù)與相關方法,如果不考慮封裝性,允許其他類調用setState()等方法,將導致在備忘錄中保存的歷史狀態(tài)發(fā)生改變,通過撤銷操作所恢復的狀態(tài)就不再是真實的歷史狀態(tài),備忘錄模式也就失去了本身的意義。
備忘錄模式實現(xiàn)框架
originator : 對象(需要保存狀態(tài)的對象)
public class Originator {
private String state;//狀態(tài)
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//編寫一個方法,可以保存一個狀態(tài)對象Memento
public Memento saveStateMemento(){
return new Memento(state);
}
public void getStateFromMemento (Memento memento){
state = memento.getState();
}
}
Memento : 備忘錄對象,負責保存好記錄,即Originator內部狀態(tài)
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
Caretaker: 守護者對象,負責保存多個備忘錄對象, 使用集合管理,提高效率
public class Caretaker {
//List集合中會有很多備忘錄對象
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento memento) {
mementoList.add(memento);
}
//獲取到第index個Origintor的備忘錄對象
public Memento get(int index){
return mementoList.get(index);
}
}
下棋案例
棋子類 Chessman,原發(fā)器角色
//原發(fā)器,需要保存對象的狀態(tài)
@Data
@AllArgsConstructor
public class Chessman
{
private String label;//當前棋子的名字: 車,炮,馬
private Integer x,y;//當前棋子的坐標
//保存當前對象的狀態(tài)--備份數(shù)據(jù)
public ChessmanMemento save()
{
return new ChessmanMemento(label,x,y);
}
//恢復當前對象的狀態(tài)
public void restore(ChessmanMemento chessmanMemento)
{
this.label=chessmanMemento.getLabel();
this.x=chessmanMemento.getX();
this.y=chessmanMemento.getY();
}
//展示當前對象的狀態(tài)
public void show()
{
System.out.println(
String.format("棋子: %s ,位置: [%d,%d]",label,x,y)
);
}
}
備忘錄角色 ChessmanMemento
//負責備份的棋子狀態(tài)
@Data
@AllArgsConstructor
public class ChessmanMemento
{
private String label;
private Integer x,y;
}
負責人角色 MementoCaretaker
//負責保存多個備份對象
public class MementoCaretaker
{
//記錄當前所處的備份狀態(tài)
Integer index=-1;//一開始沒有備份數(shù)據(jù)
//通過一個List集合保存多個備份對象
List<ChessmanMemento> chessmanMementoLinkedList= Lists.newLinkedList();
//悔棋操作--恢復到上一個備忘錄狀態(tài)
public ChessmanMemento getMemento()
{
if(index<=0)
{
throw new IndexOutOfBoundsException("已經(jīng)無棋可悔了");
}
this.index--;//當前所處的備份狀態(tài)減去一
//將當前狀態(tài)之后的狀態(tài)全部清空
//保留前index個元素,并將流收集到List中
chessmanMementoLinkedList = chessmanMementoLinkedList.stream()
.limit(this.index+1).collect(Collectors.toList());
return chessmanMementoLinkedList.get(index);
}
//下棋---增加新的備份對象
public void addMemento(ChessmanMemento chessmanMemento)
{
index++;
chessmanMementoLinkedList.add(chessmanMemento);
}
}
棋子客戶端,維護了一個 MementoCaretaker 對象
//客戶端
public class Client
{
//維護一個守護者對象
MementoCaretaker mementoCaretaker=new MementoCaretaker();
//下棋
public void play(Chessman chessman)
{
//通過調用備份返回,返回一個備份對象,添加進備份集合中去
mementoCaretaker.addMemento(chessman.save());
}
//悔棋
public void undo(Chessman chessman)
{
//得到上一次記錄的備份狀態(tài)對象
ChessmanMemento memento = mementoCaretaker.getMemento();
//調用恢復功能
chessman.restore(memento);
}
}
測試
public class Test
{
public static void main(String[] args) {
//創(chuàng)建棋子對象
Chessman chessman=new Chessman("車",1,1);
//創(chuàng)建一個客戶端
Client client=new Client();
client.play(chessman);
chessman.show();
chessman=new Chessman("馬",2,0);
client.play(chessman);
chessman.show();
//悔棋
client.undo(chessman);
chessman.show();
client.undo(chessman);
chessman.show();
}
}

備忘錄模式總結
優(yōu)點
- 它提供了一種狀態(tài)恢復的實現(xiàn)機制,使得用戶可以方便地回到一個特定的歷史步驟,當新的狀態(tài)無效或者存在問題時,可以使用暫時存儲起來的備忘錄將狀態(tài)復原。
- 備忘錄實現(xiàn)了對信息的封裝,一個備忘錄對象是一種原發(fā)器對象狀態(tài)的表示,不會被其他代碼所改動。備忘錄保存了原發(fā)器的狀態(tài),采用列表、堆棧等集合來存儲備忘錄對象可以實現(xiàn)多次撤銷操作。
缺點
- 資源消耗過大,如果需要保存的原發(fā)器類的成員變量太多,就不可避免需要占用大量的存儲空間,每保存一次對象的狀態(tài)都需要消耗一定的系統(tǒng)資源。
適用場景
- 保存一個對象在某一個時刻的全部狀態(tài)或部分狀態(tài),這樣以后需要時它能夠恢復到先前的狀態(tài),實現(xiàn)撤銷操作。
- 防止外界對象破壞一個對象歷史狀態(tài)的封裝性,避免將對象歷史狀態(tài)的實現(xiàn)細節(jié)暴露給外界對象。
注意細節(jié)
- 給用戶提供了一種可以恢復狀態(tài)的機制,可以使用戶能夠比較方便地回到某個歷史的狀態(tài)
- 實現(xiàn)了信息的封裝,使得用戶不需要關心狀態(tài)的保存細節(jié)
- 如果類的成員變量過多,勢必會占用比較大的資源,而且每一次保存都會消耗一定的內存, 這個需要注意
- 適用的應用場景:1、后悔藥。 2、打游戲時的存檔。 3、Windows 里的 ctri + z。4、IE 中的后退。4、數(shù)據(jù)庫的事務管理
- 為了節(jié)約內存,備忘錄模式可以和原型模式配合使用
參考文章
總結
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注腳本之家的更多內容!
相關文章
Mybatis中ResultMap解決屬性名和數(shù)據(jù)庫字段名不一致問題
我們Pojo類的屬性名和數(shù)據(jù)庫中的字段名不一致的現(xiàn)象時有發(fā)生,本文就詳細的介紹一下Mybatis中ResultMap解決屬性名和數(shù)據(jù)庫字段名不一致問題,感興趣的可以了解一下2021-10-10
SpringBoot集成Spring Security的方法
Spring security,是一個強大的和高度可定制的身份驗證和訪問控制框架。這篇文章主要介紹了SpringBoot集成Spring Security的操作方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
java線程池ThreadPoolExecutor實現(xiàn)原理詳解
這篇文章主要介紹了java線程池ThreadPoolExecutor實現(xiàn)原理詳解,ThreadPoolExecutor是線程池實現(xiàn)類,會動態(tài)創(chuàng)建多個線程,并發(fā)執(zhí)行提交的多個任務,需要的朋友可以參考下2023-12-12
Springboot項目基于Devtools實現(xiàn)熱部署步驟詳解
這篇文章主要介紹了Springboot項目基于Devtools實現(xiàn)熱部署,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-06-06
Spring核心容器之ApplicationContext上下文啟動準備詳解
這篇文章主要介紹了Spring核心容器之ApplicationContext上下文啟動準備詳解,ApplicationContext 繼承自 BeanFactory ,其不僅包含 BeanFactory 所有功能,還擴展了容器功能,需要的朋友可以參考下2023-11-11

