備忘錄模式可以在不破壞封裝性的前提下,實現對象狀態的保存和恢復工做,又叫快照模式或Token模式。保存對象的狀態是爲了之後在須要的時候快速恢復到保存時的狀態,所以經常使用在備份、撤銷操做上,例如編輯器裏的撤銷、遊戲裏的存檔和悔棋等功能。安全
備忘錄模式有三個組成部分:編輯器
Originagor(發起人):即須要備份的對象,能夠建立備忘錄,以及根據備忘錄來恢復狀態,能夠看到備忘錄提供的寬接口。ide
Memento(備忘錄):存儲Originator的部分或全部狀態,對外提供寬窄接口。測試
CareTaker(管理人):負責保存Memento對象,只能看到備忘錄提供的窄接口。this
上面提到了寬接口和窄接口,有必要先解釋一下,寬窄接口實際上表明瞭外界對備忘錄的訪問權限問題:spa
寬接口:可以看到備忘錄保存的全部數據,通常只對發起人可見,對其餘角色不可見。rest
窄接口:只能看到備忘錄保存的部分數據(甚至能夠實現不對外暴露任何數據),一般出於封裝和安全性考慮,對發起人以外的其餘角色只提供窄接口。code
下面以一個簡單的例子演示備忘錄模式的用法,示例模仿棋類遊戲中的悔棋,爲簡單起見,只記錄棋子的座標。對象
先定義棋子類Chessman,包含棋子的x座標和y座標:blog
public class Chessman { private int positionx; private int positiony; public void setPosition(int positionx, int positiony) { this.positionx = positionx; this.positiony = positiony; } @Override public String toString() { return "當前位置{" + "positionx=" + positionx + ", positiony=" + positiony + '}'; } public Chessman(int x, int y){ this.positionx = x; this.positiony = y; } public Memento createMemento(){ return new Memento(positionx, positiony); } public void restore(Memento memento){ this.positionx = memento.getPositionx(); this.positiony = memento.getPositiony(); } }
接着定義備忘錄類Memento,用來存儲棋子的座標信息:
public class Memento { private int positionx; private int positiony; public int getPositionx() { return positionx; } public int getPositiony() { return positiony; } public Memento(int x, int y){ this.positionx = x; this.positiony = y; } }
定義管理者類CareTaker,外界經過該類獲取備份信息:
public class CareTaker { private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
接下來用客戶端來測試這個簡單的備忘錄:
public class MementoTest { public static void main(String[] args) { Chessman chessman = new Chessman(0,0); chessman.setPosition(3,4); System.out.println(chessman); CareTaker careTaker = new CareTaker(); System.out.println("備份棋子位置。。。"); careTaker.setMemento(chessman.createMemento()); chessman.setPosition(7,5); System.out.println(chessman); System.out.println("悔棋。。。"); chessman.restore(careTaker.getMemento()); System.out.println(chessman); } }
輸出爲:
當前位置{positionx=3, positiony=4}
備份棋子位置。。。
當前位置{positionx=7, positiony=5}
悔棋。。。
當前位置{positionx=3, positiony=4}
該示例所對應的類圖結構以下:
上面這個示例只是單備份,也就是說只能備份一個狀態,將CareTaker中的Memento修改爲集合的形式能夠實現多備份。
其實上面的實現方式有一個很大的問題,就是Memento對全部的外界對象都是公開的,任何對象均可以訪問和修改Memento的字段,這種模式稱爲「白箱」模式。因爲沒有相應的權限控制,這種方式沒法保證備忘錄的安全性,不具有太大的實用價值。一種解決方案是將Memento設置爲Originator的內部類,並經過權限控制符來限制外界對他的訪問。
修改後的Chessman類,擁有私有的Memebto類:
public class ChessmanNew { private int positionx; private int positiony; public void setPosition(int positionx, int positiony) { this.positionx = positionx; this.positiony = positiony; } @Override public String toString() { return "當前位置{" + "positionx=" + positionx + ", positiony=" + positiony + '}'; } public ChessmanNew(int x, int y){ this.positionx = x; this.positiony = y; } public MementoFace createMemento(){ return new Memento(positionx, positiony); } public void restore(MementoFace memento){ this.positionx = memento.getx(); this.positiony = ((Memento)memento).getPositiony(); } private class Memento implements MementoFace{ private int positionx; private int positiony; private Memento(int x, int y){ this.positionx = x; this.positiony = y; } private int getPositiony(){ return this.positiony; } @Override public int getx() { return this.positionx; } } }
Memento對外以接口MementoFace的形式提供有限的服務(即只容許外界訪問x座標,而對外隱藏y座標),MementoFace的定義以下:
public interface MementoFace { //窄接口 int getx(); }
CareTaker類也須要作對應的修改:
public class CareTakerNew { private MementoFace mementoFace; public MementoFace getMementoFace() { return mementoFace; } public void setMementoFace(MementoFace mementoFace) { this.mementoFace = mementoFace; } }
客戶端測試以下:
public class MementoTest { public static void main(String[] args) { newtest(); } public static void newtest(){ ChessmanNew chessman = new ChessmanNew(0,0); chessman.setPosition(3,4); System.out.println(chessman); System.out.println("備份棋子位置。。。"); CareTakerNew careTaker = new CareTakerNew(); careTaker.setMementoFace(chessman.createMemento()); System.out.println(chessman); System.out.println("悔棋。。。"); chessman.restore(careTaker.getMementoFace()); System.out.println(chessman); } }
輸出與修改前的代碼一致,這裏略去。這種方式修改後,外界可以接觸到的只有MementoFace接口,只能訪問x座標,其餘什麼也作不了,從而保證了封裝性。