1. 什麼是備忘錄模式?
備忘錄模式(Memento Pattern):不破壞封裝的前提下,捕捉一個對象的內部狀態,並在這個對象之外存儲起來,從而可以在將來合適的時候把這個對象還原到存儲起來的狀態。
備忘錄模式又叫標記模式和快照模式,是對象的行爲型模式。
備忘錄對象是用來儲存在某個時刻一個對象內部狀態的對象。
採用備忘錄模式,我們可以使用一個備忘錄對象來儲存某個時刻一個對象的內部狀態,當用戶錯誤操作或者取消操作的時候能夠恢復到原來的狀態。
2. 角色
圖片來源於網絡
發起人角色(Originator):負責創建一個備忘錄對象用來記錄當前自身的內部狀態,並可以利用一個備忘錄對象恢復到原來的狀態。發起人可以決定備忘錄儲存自己的哪些狀態。
備忘錄角色(Memento):負責儲存發起人的內部狀態。一般和發起人擁有相同的成員變量。
管理者角色(Caretaker):負責管理備忘錄對象,但是不能更改備忘錄對象的內部狀態。
3. 優點
(1)給用戶提供了一種可以恢復狀態的機制,可以使用戶很方便的還原到某個歷史狀態。
(2)實現了信息的封裝,用戶不用關心備忘錄對象的保存細節。準守“迪米特法則”。
4. 缺點
消耗系統資源。當對象的成員變量很多的時候,每保存一個備忘錄對象就會消耗更多的系統資源。
5. 使用場景
(1)遊戲存檔。
(2)撤銷操作。
(3)類似於操作系統的Ctrl+Z操作。
(4)數據庫的事務管理。數據庫出現問題可以還原到最近的備份點。
6. 示例代碼
6.1 單狀態單備份
此種方式是最簡單的備忘錄模式。發起人只有一個內部狀態,而且只能備份一個備忘錄。
此例子採用“白箱實現”,備忘錄對象對任何對象都提供一個寬接口(允許訪問所有數據),備忘錄對象存儲的所有內部狀態對所有對象都是公開的,是具有破壞封裝性的,這種方式就成爲“白箱實現”。
(1)發起人類
/**
* 發起人角色類
* 發起人新建一個備忘錄對象將自己的狀態儲存起來
*/
public class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
/**
* 返回一個新的備忘錄對象
*/
public Memento createMemento(){
return new Memento(this.state);
}
/**
* 將發起人對象恢復到備忘錄儲存的狀態
*/
public void restoreMemento(Memento memento){
this.state=memento.getState();
}
}
(2)備忘錄類
/**
* 備忘錄角色
* 將發起人角色傳入的狀態儲存起來
*/
public class Memento {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento(String state) {
this.state=state;
}
}
(3)管理者類
/**
* 負責人角色類
* 負責人負責保存備忘錄對象
* 但是負責人不修改、查看備忘錄對象的內容
* (注意,因爲備忘錄對象是完全暴露給所有接口的,
* 此處負責人是有能力修改備忘錄的狀態的。)
*/
public class Caretaker {
private Memento memento;
public Memento retrieveMemento() {
return memento;
}
public void saveMemento(Memento memento) {
this.memento = memento;
//由於備忘錄爲所有對象提供了寬接口,因此管理者也可以修改備忘錄的狀態
//memento.setState("CCC");
//System.out.println("負責人修改了備忘錄對象狀態爲CCC");
}
}
(4)測試
public class Client {
public static void main(String[] args) {
//new發起人對象
Originator ori=new Originator();
ori.setState("AAA");
System.out.println("發起人第一次的狀態:"+ori.getState());
//new負責人對象
Caretaker caretaker=new Caretaker();
//保存備忘錄對象
caretaker.saveMemento(ori.createMemento());
//發起人對象更改狀態
ori.setState("BBB");
System.out.println("發起人修改後的狀態:"+ori.getState());
//回覆發起人之前狀態
ori.restoreMemento(caretaker.retrieveMemento());
System.out.println("發起人恢復後的狀態:"+ori.getState());
}
}
(5)測試結果
發起人第一次的狀態:AAA
發起人修改後的狀態:BBB
發起人恢復後的狀態:AAA
6.2 多狀態單備份
此示例的發起人具有多個內部狀態,此處採用“黑箱實現”。備忘錄對象爲發起人提供一個寬接口,爲管理者或其他對象提供一個窄接口(只允許傳遞對象,不允許訪問數據),這種隔離訪問的方式稱爲java雙接口,這種實現方式稱爲“黑箱實現”。Java語言中實現雙接口的方式可以將備忘錄對象設置成發起人對象的內部類,然後讓備忘錄對象實現一個標記接口。
(1)發起人類
/**
* 發起人角色類
*/
public class Originator {
private String state;
private String state2;
public String getState2() {
return state2;
}
public void setState2(String state2) {
this.state2 = state2;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
/**
* 返回一個新的備忘錄對象
*/
public IMemento createMemento(){
return new Memento(this.state,this.state2);
}
/**
* 將發起人對象恢復到備忘錄儲存的狀態
*/
public void restoreMemento(IMemento memento){
this.state=((Memento)memento).getState();
this.state2=((Memento)memento).getState2();
}
/**
* 備忘錄對象類,是發起人的內部類
* 所有方法和屬性都是private私有的,
* 因此只有他自己和發起人對象能調用
*/
private class Memento implements IMemento{
private String state;
private String state2;
public String getState2() {
return state2;
}
public void setState2(String state2) {
this.state2 = state2;
}
private String getState() {
return state;
}
private void setState(String state) {
this.state = state;
}
private Memento(String state,String state2) {
this.state=state;
this.state2=state2;
}
}
}
(2)備忘錄接口
/**
* 窄接口IMemento,
* 這是一個標識接口,
* 因此它沒有定義出任何的方法。
*/
public interface IMemento {}
(3)管理者類
/**
* 負責人角色類
* 負責人角色類Caretaker通過IMemento接口管理備忘錄對象,
* 由於這個接口僅僅是一個標識接口,
* 因此負責人角色不可能改變這個備忘錄對象的內容。
*/
public class Caretaker {
private IMemento memento;
public IMemento retrieveMemento() {
return memento;
}
public void saveMemento(IMemento memento) {
this.memento = memento;
}
}
(4)測試
public class Client {
public static void main(String[] args) {
//new發起人對象
Originator ori=new Originator();
ori.setState("AAA");
ori.setState2("1");
System.out.println("發起人第一次的狀態:"+ori.getState()+","+ori.getState2());
//new負責人對象
Caretaker caretaker=new Caretaker();
//保存備忘錄對象
caretaker.saveMemento(ori.createMemento());
//發起人對象更改狀態
ori.setState("BBB");
ori.setState2("2");
System.out.println("發起人修改後的狀態:"+ori.getState()+","+ori.getState2());
//回覆發起人之前狀態
ori.restoreMemento(caretaker.retrieveMemento());
System.out.println("發起人恢復後的狀態:"+ori.getState()+","+ori.getState2());
}
}
(5)測試結果
發起人第一次的狀態:AAA,1
發起人修改後的狀態:BBB,2
發起人恢復後的狀態:AAA,1
6.3 多狀態多備份
一般在使用備忘錄模式的時候,發起人通常是一個對象,對象中的變量不只一個,需要備份的狀態也不只一份,這就是多狀態多備份備忘錄。此示例也採用“黑箱實現”。
(1)發起人類
/**
* 發起人角色類
*/
public class Originator {
private String state;
private String state2;
public String getState2() {
return state2;
}
public void setState2(String state2) {
this.state2 = state2;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
/**
* 返回一個新的備忘錄對象
*/
public IMemento createMemento(){
return new Memento(this.state,this.state2);
}
/**
* 將發起人對象恢復到備忘錄儲存的狀態
*/
public void restoreMemento(IMemento memento){
this.state=((Memento)memento).getState();
this.state2=((Memento)memento).getState2();
}
/**
* 備忘錄對象類,是發起人的內部類
* 所有方法和屬性都是private私有的,
* 因此只有他自己和發起人對象能調用
*/
private class Memento implements IMemento{
private String state;
private String state2;
public String getState2() {
return state2;
}
public void setState2(String state2) {
this.state2 = state2;
}
private String getState() {
return state;
}
private void setState(String state) {
this.state = state;
}
private Memento(String state,String state2) {
this.state=state;
this.state2=state2;
}
}
}
(2)備忘錄接口
/**
* 窄接口IMemento,
* 這是一個標識接口,
* 因此它沒有定義出任何的方法。
*/
public interface IMemento {}
(3)管理者類
/**
* 負責人角色類
* 負責人角色類Caretaker通過IMemento接口管理備忘錄對象,
* 由於這個接口僅僅是一個標識接口,
* 因此負責人角色不可能改變這個備忘錄對象的內容。
*/
public class Caretaker {
//儲存多個備忘錄對象
private HashMap<String, IMemento> mementos=new HashMap<String,IMemento>();
public IMemento retrieveMemento(String key) {
return mementos.get(key);
}
public void saveMemento(String key,IMemento memento) {
this.mementos.put(key, memento);
}
}
(4)測試
public class Client {
public static void main(String[] args) {
// new發起人對象
Originator ori = new Originator();
ori.setState("中國");
ori.setState2("富強");
System.out.println("發起人第1次的狀態:" + ori.getState() + "," + ori.getState2());
// new負責人對象
Caretaker caretaker = new Caretaker();
// 保存備忘錄對象
caretaker.saveMemento("1", ori.createMemento());
// 發起人對象更改狀態
ori.setState("依法治國");
ori.setState2("民主和諧");
System.out.println("發起人第2次的狀態:" + ori.getState() + "," + ori.getState2());
caretaker.saveMemento("2", ori.createMemento());
// 發起人對象再次變更狀態
ori.setState("軟件");
ori.setState2("發達");
System.out.println("發起人第3次的狀態:" + ori.getState() + "," + ori.getState2());
caretaker.saveMemento("3", ori.createMemento());
// 恢復發起人第一次狀態
ori.restoreMemento(caretaker.retrieveMemento("1"));
System.out.println("發起人恢復第1次後的狀態:" + ori.getState() + "," + ori.getState2());
// 恢復發起人第3次狀態
ori.restoreMemento(caretaker.retrieveMemento("3"));
System.out.println("發起人恢復第3次後的狀態:" + ori.getState() + "," + ori.getState2());
}
}
(5)測試結果
發起人第1次的狀態:中國,富強
發起人第2次的狀態:依法治國,民主和諧
發起人第3次的狀態:軟件,發達
發起人恢復第1次後的狀態:中國,富強
發起人恢復第3次後的狀態:軟件,發達
參考文章 http://www.cnblogs.com/java-my-life/archive/2012/06/06/2534942.html
http://www.runoob.com/design-pattern/memento-pattern.html
http://alaric.iteye.com/blog/1931253
【四川樂山程序員聯盟,歡迎大家加羣相互交流學習5 7 1 8 1 4 7 4 3】