定義
備忘錄模式又叫做快照模式(Snapshot Pattern)或Token模式,是對象的行為模式。
備忘錄對象是一個用來存儲另外一個對象內部狀態的快照的對象。備忘錄模式的用意是在不破壞封裝的條件下,將一個對象的狀態捕捉(Capture)住,并外部化,存儲起來,從而可以在將來合適的時候把這個對象還原到存儲起來的狀態,備忘錄模式常常與命令模式和迭代子模式一同使用。
備忘錄模式的結構
備忘錄模式的結構圖如下所示:
備忘錄模式所涉及的角色有三個:備忘錄角色(Memonto)、發起人角色(Originator)、負責人角色(Caretaker)。
備忘錄角色(Memento)
備忘錄角色有如下責任:
- 將發起人(Originator)對象的內部狀態存儲起來。備忘錄可以根據發起人對象的判斷來決定存儲多少個發起人(Originator)對象的內部狀態。
- 備忘錄可以保護其內容不被發起人(Originator)對象之外的任何對象所讀取。
備忘錄有兩個等效的接口:
- 窄接口:負責人(Caretaker)對象(和其他出發起人對象之外的任何對象)看到的是備忘錄的窄接口(narror Interface),這個窄接口只允許他把備忘錄對象傳給其他的對象。
- 寬接口:與負責人看到的窄接口相反的是,發起人對象可以看到一個寬接口(wide Interface),這個寬接口允許它讀取所有的數據,以便根據這些數據恢復這個發起人對象的內部狀態。
發起人角色(Originator)
發起人角色有如下責任:
- 創建一個含有當前內部狀態的備忘錄對象
- 使用備忘錄對象存儲其內部狀態。
負責人角色(Caretaker)
負責人角色有如下責任:
- 負責保存備忘錄對象。
- 不檢查備忘錄對象的內容
“白箱”備忘錄模式的實現
備忘錄角色對任何對象都提供一個接口,即寬接口,備忘錄角色的內部所存儲的狀態就對所有對象公開。因此這個實現又叫做“白箱實現”。
“白箱”實現將發起人角色的狀態存儲在一個大家都看得到的地方,因此是破壞封裝性的。但是通過程序員自律,同樣可以在一定程度上實現模式的大部分用意。因此白箱實現的仍然是有意義的。
下面給出一個示意性的“白箱實現”
示例代碼
備忘錄角色類,備忘錄對象將發起人對象傳入的狀態存儲起來。
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
發起人角色類,發起人角色利用一個新創建的備忘錄對象將自己的內部狀態存儲起來。
public class Originator {
private String state;
/**
* 工廠方法,返回一個新的備忘錄對象
* @return
*/
public Memento createMemento() {
return new Memento(state);
}
/**
* 將發起人的狀態恢復到備忘錄對象所記錄的狀態
* @param memento
*/
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
System.out.println("當前狀態:" + this.state);
}
}
負責人角色類,負責人角色類負責保存備忘錄對象,但是從不修改(甚至不查看)備忘錄對象的內容。
public class Caretaker {
private Memento memento;
/**
* 備忘錄的取值方法
* @return
*/
public Memento retrieveMenento() {
return this.memento;
}
/**
* 備忘錄的賦值方法
* @param memento
*/
public void saveMemento(Memento memento) {
this.memento = memento;
}
}
客戶端角色類
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
//改變發起人對象的狀態
originator.setState("On");
//創建備忘錄對象,并將發起人對象的狀態存儲起來
caretaker.saveMemento(originator.createMemento());
//修改發起人對象的狀態
originator.setState("Off");
//恢復發起人對象的狀態
originator.restoreMemento(caretaker.retrieveMenento());
//發起人對象的狀態
System.out.println("發起人對象的當前狀態為:" + originator.getState());
}
}
從上面的這個示意性的客戶端角色里面,首先將發起人對象的狀態設置成“On”,并創建一個備忘錄對象將這個狀態存儲起來;然后將發起人對象的狀態更改為“Off”;最后將發起人對象的狀態恢復到備忘錄對象所存儲起來的狀態,即“On”狀態。
系統的時序圖更能夠反映出系統中各個角色被調用的時間順序。如下圖是將發起人對象的狀態存儲到白箱備忘錄對象中的時序圖。
可以看出系統運行的時序是這樣的:
- 將發起人對象的狀態設置為“On”。
- 調用發起人角色的
createMemento()
方法,創建一個備忘錄的對象將這個狀態存儲起來。 - 將備忘錄對象存儲到負責人對象中去。
將發起人對象恢復到備忘錄對象中所記錄的狀態的時序圖如下所示:
- 將發起人的狀態設置成“Off”。
- 將備忘錄對象從負責人對象中取出。
- 將發起人對象恢復到備忘錄對象存儲的狀態,也就是發起人對象的“On”狀態。
“黑箱”備忘錄模式的實現
備忘錄角色對發起人Originator
角色對象提供一個寬接口,而為其他對象提供一個窄接口。這樣的實現叫做“黑箱實現”。
在Java語言中,實現雙重接口的辦法就是講備忘錄角色類設計成發起人角色類的內部成員類。
將Memento
設成Originator
類的內部類,從而將Memento
對象封裝在Originator
里面;在外面提供一個標識接口MementoIF
給Caretaker
及其他對象。這樣Originator
類看到的是Memento
所有的接口,而Caretaker
吉其他對象看到的僅僅是標識接口MementoIF
所暴露出來的借口。
使用內部類實現備忘錄模式的類圖如下所示:
示例代碼
窄接口MementoIF
,這是一個標識接口,因此沒有定義出任何的方法。
public interface MementoIF {
}
發起人角色類Originator
中定義了一個內部類Memento
,由此Memento
類的全部接口都是私有的,因此只有它自己和發起人角色對象類可以調用。
public class Originator {
private String state;
/**
* 將發起人的狀態恢復到備忘錄對象所記錄的狀態
* @param memento
*/
public void restoreMemento(MementoIF memento) {
this.state = ((Memento)memento).getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
System.out.println("當前狀態:" + this.state);
}
/**
* 工廠方法,返回一個新的備忘錄對象
* @return
*/
public MementoIF createMemento() {
return new Memento(this.state);
}
private class Memento implements MementoIF {
private String state;
/**
* 構造方法
* @param state
*/
private Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
}
負責人角色類Caretaker
能夠得到的備忘錄對象是以MementoIF
為接口的,由于這個接口僅僅是一個標識接口,因此負責人角色不可能改變這個備忘錄對象的內容。
public class Caretaker {
private MementoIF memento;
/**
* 備忘錄的取值方法
* @return
*/
public MementoIF retrieveMenento() {
return this.memento;
}
/**
* 備忘錄的賦值方法
* @param memento
*/
public void saveMemento(MementoIF memento) {
this.memento = memento;
}
}
客戶端角色類
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
//改變發起人對象的狀態
originator.setState("On");
//創建備忘錄對象,并將發起人對象的狀態存儲起來
caretaker.saveMemento(originator.createMemento());
//修改發起人對象的狀態
originator.setState("Off");
//恢復發起人對象的狀態
originator.restoreMemento(caretaker.retrieveMenento());
//發起人對象的狀態
System.out.println("發起人對象的當前狀態為:" + originator.getState());
}
}
執行流程為:
- 客戶端首先將發起人角色的狀態設置為“On”;
- 然后調用發起人角色的
createMemento()
方法,創建一個備忘錄對象將發起人角色的狀態存儲起來(這個方法返回一個MementoIF
接口,真實的數據類型為Originator
內部類的Memento
對象)。 - 將備忘錄對象存儲到負責人對象中去,由于負責人對象存儲的僅僅是
MementoIF
接口,因此無法獲取備忘錄對象內部存儲的狀態。 - 將發起人對象的狀態設置為“Off”。
- 調用負責人對象的
restoreMemento()
方法將備忘錄對象取出。注意,此時僅能或得到的返回結果為MementoIF
接口,因此無法讀取此對象的內部狀態。 - 調用發起人對象的
retrieveMenento()
方法將發起人對象的狀態恢復到備忘錄對象存儲的狀態上,也就是“On”狀態。由于發起人對象的內部類Memento
實現了MementoIF
接口,這個內部類是傳入的備忘錄對象的真實類型,因此發起人對象可以利用內部類Memento
的私有接口讀出此對象的內部狀態。
多重檢查點
前面所給出的白箱和黑箱的示意性實現都是只存儲一個狀態的簡單實現,也可以叫做只有一個檢查點。常見的系統往往需要存儲不止一個狀態,而是需要存儲多個狀態,或者叫做多個檢查點。
備忘錄模式可以將發起人對象的狀態存儲到備忘錄對象里面,備忘錄模式可以將發起人對象恢復到備忘錄對象所存儲的某一個檢查點上。下面給出一個示意性的、有多重檢查點的備忘錄模式的實現。
示例代碼
備忘錄角色類,這個實現可以存儲任意多的狀態,外界可以使用檢查點指數index來取出檢查點上的狀態:
public class Memento {
private List<String> states;
private int index;
/**
* 構造方法
* @param states
* @param index
*/
public Memento(List<String> states, int index) {
//該處需要注意,我們在這里重新構建了一個新的集合,拷貝狀態集合到新的集合中,保證原有集合變化不會影響到我們記錄的值
this.states = new ArrayList<>(states);
this.index = index;
}
public List<String> getStates() {
return states;
}
public int getIndex() {
return index;
}
}
發起人角色
public class Originator {
private List<String> states;
//檢查點序號
private int index;
/**
* 構造函數
*/
public Originator() {
this.states = new ArrayList<>();
index = 0;
}
/**
* 工廠方法,返回一個新的備忘錄對象
* @return
*/
public Memento createMemento() {
return new Memento(states, index);
}
/**
* 將發起人恢復到備忘錄對象記錄的狀態上。
* @param memento
*/
public void restoreMemento(Memento memento) {
this.states = memento.getStates();
this.index = memento.getIndex();
}
/**
* 狀態的賦值方法
* @param state
*/
public void setState(String state) {
this.states.add(state);
this.index++;
}
public List<String> getStates() {
return states;
}
/**
* 輔助方法,打印所有狀態
*/
public void pringStates() {
System.out.println("當前檢查點共有:" + states.size() + "個狀態值");
for (String state : states) {
System.out.println(state);
}
}
}
負責人角色類
public class Caretaker {
private Originator originator;
private List<Memento> mementos = new ArrayList<>();
private int current;
/**
* 構造函數
* @param originator
*/
public Caretaker(Originator originator) {
this.originator = originator;
this.current = 0;
}
/**
* 創建一個新的檢查點
* @return
*/
public int createMemento() {
Memento memento = this.originator.createMemento();
this.mementos.add(memento);
return this.current++;
}
/**
* 將發起人對象狀態恢復到某一個檢查點
* @param index
*/
public void restoreMemento(int index) {
Memento memento = mementos.get(index);
originator.restoreMemento(memento);
}
/**
* 將某一個檢查點刪除
* @param index
*/
public void removeMemento(int index) {
mementos.remove(index);
}
public void printAll() {
for (int i = 0; i < mementos.size(); i++) {
System.out.println("index i : " + i + " : " + mementos.get(i) + " : " + mementos.get(i).getStates());
System.out.println("---------------------------------");
}
}
}
客戶端角色類
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker(originator);
//改變狀態
originator.setState("State 0");
//建立一個檢查點
caretaker.createMemento();
//改變狀態
originator.setState("State 1");
//建立一個檢查點
caretaker.createMemento();
//改變狀態
originator.setState("State 2");;
//建立一個檢查點
caretaker.createMemento();
//改變狀態
originator.setState("State 3");
//建立一個檢查點
caretaker.createMemento();
//改變狀態
originator.setState("State 4");
//建立一個檢查點
caretaker.createMemento();
//打印出所有的檢查點
originator.pringStates();
System.out.println("---------恢復狀態到某一個檢查點----------");
//恢復到第二個檢查點
caretaker.restoreMemento(1);
//打印出所有的檢查點.
originator.pringStates();
}
}
運行結果如下:
當前檢查點共有:5個狀態值
State 0
State 1
State 2
State 3
State 4
---------恢復狀態到某一個檢查點----------
當前檢查點共有:2個狀態值
State 0
State 1
可以看出,客戶端角色通過不斷改變發起人角色的狀態,并將之存儲在備忘錄角色里面。通過指明檢查點指數可以將發起人角色恢復到相應檢查點所對應的狀態上。
將發起人的狀態存儲到備忘錄對象中的時序圖如下:
系統運行時的時序是這樣的:
- 將發起人的狀態設置為某個有效狀態,發起人會記錄當前已經設置的狀態值列表
- 調用負責人角色的
createMemento()
方法創建備忘錄角色存儲點,在該方法中,會調用發起人角色的createMemento()
創建一個備忘錄對象,記錄當時發起人對象中的狀態值列表和序號。然后負責人角色對象將發起人對象返回的備忘錄對象存儲起來。
將發起人對象恢復到某一個檢查點備忘錄對象的時序圖如下:
由于負責人角色的功能被增強了,因此將發起人對象恢復到備忘錄對象所記錄的狀態時,系統運行的時序被簡化了。
- 調用負責人對象的
restoreMemento()
方法,指定恢復到的檢查點。 - 負責人對象從存儲的備忘錄對象列表中獲取相應檢查點位置的備忘錄對象,調用發起人對象的
restoreMemento()
方法 - 在方法內將備忘錄中保存的狀態集合和序號恢復到發起人對象上。
"自述歷史"模式
所謂“自述歷史”模式(History-On-Self Pattern)實際上就是備忘錄模式的一個變種。在備忘錄模式中,發起人角色Originator
、負責人角色Caretaker
和備忘錄角色Mementor
都是獨立的角色。雖然在實現上備忘錄角色類可以成為發起人角色類的內部成員類,但是備忘錄角色類仍然保持作為一個角色的獨立意義。
在“自述歷史”模式里面,發起人角色自己兼任負責人角色。
“自述歷史”模式的類圖如下所示:
備忘錄角色有如下責任:
- 將發起人對象
Originator
的內部狀態存儲起來。 - 備忘錄角色可以保護其內容不被發起人
Originator
對象之外的任何對象所讀取。
發起人角色有如下責任:
- 創建一個含有它當前的內部狀態的備忘錄對象。
- 使用備忘錄對象存儲其內部狀態。
客戶端角色有負責保護備忘錄對象的責任。
示例代碼
窄接口MementoIF
,這是一個標識接口,因此沒有定義任何方法
public interface MementoIF {
}
發起人角色類,發起人角色同時還要兼任負責人角色,也就是說她自己負責保持自己的備忘錄對象。
public class Originator {
private String state;
/**
* 狀態變更
* @param state
*/
public void changeState(String state) {
this.state = state;
System.out.println("狀態改變為:" + this.state);
}
/**
* 工廠方法,返回一個新的備忘錄對象
* @return
*/
public Memento createMemento() {
return new Memento(this);
}
/**
* 將發起人狀態恢復到備忘錄對象所記錄的狀態上
* @param memento
*/
public void restoreMemento(MementoIF memento) {
changeState(((Memento)memento).getState());
}
public class Memento implements MementoIF {
private String state;
/**
* 構造方法
* @param state
*/
private Memento(Originator originator) {
this.state = originator.state;
}
private String getState() {
return this.state;
}
}
}
客戶端角色類
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
//修改狀態
originator.changeState("State 0");
//創建備忘錄
MementoIF memento = originator.createMemento();
//修改狀態
originator.changeState("State 1");
//按照備忘錄對象存儲的狀態恢復發起人對象的狀態
originator.restoreMemento(memento);
}
}
由于“自述歷史”作為一個備忘錄模式的特殊實現,實現形式非常簡單易懂,因此它可能是備忘錄模式最為流行的實現形式。