Memento模式(備忘錄設計模式)
Memento模式?
使用面向物件程式設計的方式實現撤銷功能時,需要事先儲存例項的相關狀態資訊。然後,在撤銷時,還需要根據所儲存的資訊將例項恢復至原來的狀態。這個時候你需要使用Memento設計模式。(以及例項實現對狀態的儲存)
- 關鍵字:
1.·Undo(撤銷)
2.·Redo(重做)
3.·History(歷史記錄)
4。·Snapshot(快照) -
破壞封裝性:
將依賴於例項內部結構的程式碼分散地編寫在程式中的各個地方,導致程式變得難以維護。
- 寬窄介面
- wide interface——寬介面(APl)Memento角色提供的“寬介面(API)”是指所有用於獲取恢復物件狀態資訊的方法的集合。由於寬介面(API)會暴露所有Memento角色的內部資訊,因此能夠使用寬介面(API)的只有Originator角色。
- narrowinterface——窄介面(API)Memento角色為外部的Caretaker角色提供了“窄介面(API)”。可以通過窄介面(API)獲取的Memento角色的內部資訊非常有限,因此可以有效地防止資訊洩露。
通過對外提供以上兩種介面(API),可以有效地防止物件的 封裝性被破壞
-
相關設計模式
1.Command模式(第22章)在使用Command模式處理命令時,可以使用Memento模式實現撤銷功能。
2.Protype模式(第6章)在Memento模式中,為了能夠實現快照和撤銷功能,儲存了物件當前的狀態。儲存的資訊只是在恢復狀態時所需要的那部分資訊。
而在Protype模式中,會生成一個與當前例項完全相同的另外一個例項。這兩個例項的內容完全一樣。
- State模式(第19章)在Memento模式中,是用“例項”表示狀態。而在State模式中,則是用“類”表示狀態。
理清職責
- 實現功能
- ·遊戲是自動進行的
- ·遊戲的主人公通過擲骰子來決定下一個狀態
- ·當骰子點數為1的時候,主人公的金錢會增加·當骰子點數為2的時候,主人公的金錢會減少
- ·當骰子點數為6的時候,主人公會得到水果
- ·主人公沒有錢時遊戲就會結束
包====>>>名字=====>>>說明
game |Memento|表示Gamer狀態的類
game |Gamer表示遊戲主人公的類。它會生成Memento的例項進行遊戲的類。它會事先儲存Memento的例項,之後會根據需要恢復Gamer的狀態
null | MainT 這裡為了方便起見使用MainT作為責任人儲存使用者狀態
UML
時序圖:

Code
- Gamer
public class Gamer { /** * 下面的money 與 fruits 就是按照一般的定義方式去定義 * 但是我們提取Memento的時候需要注意這個的獲取規則 */ // 獲得金錢 private int money; // 獲得的水果 private List<String> fruits=new ArrayList<>(); private Random random=new Random(); private final static String[] fruitname=new String[]{ "蘋果","葡萄","香蕉","橘子" }; public Gamer(int money) { this.money = money; } public int getMoney() { return money; } /** * 開始遊戲 * 骰子結果1,2 ,6進行不同的操作 */ public void bet(){ int dice=random.nextInt(6)+1; if(dice==1){ this.money+=100; System.out.println("金錢增加了!"); }else if(dice==2){ this.money/=2; System.out.println("金錢減半了!"); }else if(dice==6){ String f=getFruit(); System.out.println("獲得了水果["+f+"]!"); this.fruits.add(f); }else{ System.out.println("什麼也不發生"); } } /** * 快照方法 */ public Memento createMemento(){ Memento memento = new Memento(this.money); Iterator<String> iterator = fruits.iterator(); while (iterator.hasNext()){ String s = iterator.next(); if(s.startsWith("好吃的")){ memento.addFruit(s); } } return memento; } /** * 撤銷方法 */ public void restoreMemento(Memento memento){ this.money=memento.money; this.fruits=memento.fruits; } private String getFruit() { String prefix=""; if(random.nextBoolean()){ prefix="好吃的"; } return prefix+fruitname[random.nextInt(fruitname.length)]; } @Override public String toString() { return "Gamer{" + "money=" + money + ", fruits=" + fruits + '}'; } }
- Memento
public class Memento { /** * 使用過程中因為Memento與Gamer是強關聯關係,但是又因為是在同一個game包下, * 使用可見性修飾符顯得比較重要: * 這裡的兩個欄位在同一個包下都是可以訪問 */ int money; ArrayList<String> fruits; /** * 窄介面 */ public int getMoney(){ return money; } /** * 這裡是寬介面 * @param money */ Memento(int money) { this.money = money; this.fruits = new ArrayList<>(); } /** * 這裡是寬介面 */ void addFruit(String fruit){ fruits.add(fruit); } /** * 這裡是寬介面 */ ArrayList<String> getFruits(){ return (ArrayList<String>) fruits.clone(); } }
- MainT
public class MainT { /** * 這裡的狀態只是單個快照點,當你需要多個快照點的時候, * 單獨建立一個snapshot類來管理,可以使用集合等, * 這裡寫個例子 */ public static void main(String[] args) { Gamer gamer = new Gamer(100); //儲存的一個快照 初始狀態 Memento memento = gamer.createMemento(); for (int i = 0; i < 100; i++) { System.out.println("===="+i); System.out.println("當前狀態"+gamer); //開始遊戲 gamer.bet(); System.out.println("還有多少錢"+gamer.getMoney()+"元"); if(gamer.getMoney()>memento.getMoney()){ System.out.println("//儲存新狀態"); memento=gamer.createMemento(); }else if(gamer.getMoney()<memento.getMoney()/2){ System.out.println("金錢減少一半了,恢復到原來的狀態"); gamer.restoreMemento(memento); } try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } } } } public class SnapShotManger implements Serializable { private List<Memento> mementos=new ArrayList<>(); /** * 實現java.io.Serializable介面 * 用objectoutputstream的writeobject方法 * 用objectInputStream的 readobject方法 */ /** * 儲存 */ /** * 恢復 */ }