備忘錄模式在不破壞封閉的前提下,捕獲一個對象的内部狀态,并在該對象之外儲存這個狀态。這樣以後就可将該對象恢複到原先儲存的狀态。
一、備忘錄模式
我們編輯word文檔、寫這篇部落格的時候,經常用到 ctrl+z 這個快捷鍵,就是撤銷目前操作回到上一步編輯狀态。還有一些線上考試系統,在考到中間的時候退出,再重進還能在上一次的基礎上接着做題。這裡面都有一個環節,那就是回複到之前的某個節點,這個怎麼實作的呢?
其中一種方式就是通過備忘錄模式去實作,它能儲存對象目前狀态,并在之後恢複到此狀态(後悔藥),當然它也保證被儲存的對象狀态不能被外部通路,保證内部完整性,不向外透露。
角色
- 發起人: 發起人的内部要規定要備忘的範圍,負責提供備案資料
- 備忘錄: 存儲發起人對象的内部狀态,在需要的時候,可以向其他人提供這個内部狀态,以友善負責人恢複發起人狀态
- 負責人: 負責對備忘錄進行管理(儲存或提供備忘錄)
二、代碼實作
這裡就以考試系統為例,中途退出再進入。
Originator 建立者:
/**
* Originator 建立者
* 其内部可以實作備忘錄的建立以及恢複
* 負責建立一個備忘錄,用來記錄、恢複自身的内部狀态。同時根據需要決定Memento存儲自身的那些狀态
*
* 考試系統(Originator)
*/
@ToString
public class ExaminationSystem {
// 題目序号
private int topicNum = 1;
// 剩餘時間
private int remainingTime = 120;
// 答案緩存
private String answer = "";
// 開始答題
public void startTest(){
Console.log("開始答題:" + String.format("第%d題", topicNum));
remainingTime -=10;
Console.log("時間還剩" + remainingTime + "分鐘");
answer = "答案1";
topicNum++;
Console.log("開始做"+String.format("第%d題", topicNum));
}
// 退出考試
public void quit(){
Console.log("---------------------");
Console.log("退出前的情況:"+this.toString());
Console.log("退出考試");
Console.log("---------------------");
}
// 建立備忘錄
public Memento createMemoto(){
Memento memento = new Memento();
memento.topicNum = topicNum;
memento.remainingTime = remainingTime;
memento.answer = answer;
return memento;
}
// 擷取備忘錄,恢複當時的對象狀态
public void restore(Memento memento){
this.topicNum = memento.topicNum;
this.remainingTime = memento.remainingTime;
this.answer = memento.answer;
System.out.println("恢複後的考試屬性:"+this.toString());
}
}
Memento 備忘錄類:
/**
* 備忘錄類
*/
@ToString
public class Memento {
public int topicNum;
public int remainingTime;
public String answer;
}
/**
* 負責管理 Memento
*/
public class Caretaker {
//備忘錄
Memento memento;
//存檔
public void archive(Memento memoto){
this.memento = memoto;
}
//擷取存檔
public Memento getMemoto(){
return memento;
}
}
public class Client {
public static void main(String[] args) {
// 建構考試系統對象(Originator)
ExaminationSystem examination = new ExaminationSystem();
//開始考試
examination.startTest();
//建構caretaker,用于試卷存檔
Caretaker caretaker = new Caretaker();
//通過考試系統本身建立備忘錄,caretaker執行存檔操作
caretaker.archive(examination.createMemoto());
//退出考試
examination.quit();
//重新開啟考試,并通過caretaker恢複考試進度
ExaminationSystem examination_new = new ExaminationSystem();
examination_new.restore(caretaker.getMemoto());
}
}
開始答題:第1題
時間還剩110分鐘
開始做第2題
---------------------
退出前的情況:ExaminationSystem(topicNum=2, remainingTime=110, answer=答案1)
退出考試
---------------------
恢複後的考試屬性:ExaminationSystem(topicNum=2, remainingTime=110, answer=答案1)
三、适用場景
上面的介紹可以看到備忘錄模式可以儲存一份對象的快照,一般來說,一個對象有一個對應的備忘對象,記錄對象中要備忘的字段,而多個對象的備忘,同一由同一個負責人進行管理,可以用 Map 來做到這一點,負責人中的備忘容器是一個 Map 類型的資料,值是一個實作備忘接口的資料結構即可。
有了這個快照那就等于有了“後悔藥”,比如可以暫時将遊戲存檔,可以撤銷操作回到之前的狀态,再比如把儲存的備忘錄序列化成字元串儲存在磁盤中,下次啟動的時候從磁盤中擷取狀态,這樣就可以做一個簡單的快照了,等等。
- 優點:
- 備忘錄模式僅做資料備忘,不論該資料是否正确。
- 設計模式最大的優點就是解耦,各司其職,發起人隻需要提供備忘資料,不需要對其進行管理
- 缺點
- 實際應用中,備忘錄模式大多是多狀态的,如果進行大量備忘的話,會占用大量記憶體,當然,如果持久化在磁盤中的話,會減少記憶體占用,但會增加IO操作,這就需要開發者根據實際業務情況進行取舍了。
使用場景
- 需要儲存一個對象在某一個時刻的狀态或者部分狀态
- 如果用一個接口來讓其他對象的到這些狀态,将會暴露這個對象的實作細節并破壞對象的的封裝性,一個對象不希望外界直接通路其内部狀态,通過中間對象可以間接通路其内部狀态