Memento 模式
使用Memento模式可以實現應用程序的以下功能。
- Undo(撤銷)
- Redo(重做)
- History(歷史記錄)
- Snapshot(快照)
Memento模式事先將某個時間點的實例的狀態保存下來,之後在必要時,再將實例恢復至當時的狀態。
示例程序
類的一覽表
名字 | 說明 |
---|---|
Memento | 表示Gamer狀態的類 |
Gamer | 表示遊戲主人公的類。它會生成Memento的實例 |
Main | 進行遊戲的類。它會事先保存Memento的實例,之後會根據需要恢復Gamer的狀態。 |
示例程序類圖
程序功能:
- 遊戲是自動進行的
- 遊戲的主人公通過擲骰子來決定下一個狀態
- 當骰子點數爲1的時候,主人公的金錢會增加
- 當骰子點數爲2的時候,主人公的金錢會減少
- 當骰子點數爲6的時候,主人公會得到水果
- 主人公沒有錢的時候遊戲會結束
Memento
import java.util.*;
public class Memento {
int money; // 所持金錢
ArrayList fruits; // 當前獲得的水果
public int getMoney() { // 獲取當前所持金錢(narrow interface)
return money;
}
Memento(int money) { // 構造函數(wide interface)
this.money = money;
this.fruits = new ArrayList();
}
void addFruit(String fruit) { // 添加水果(wide interface)
fruits.add(fruit);
}
List getFruits() { // 獲取當前所持所有水果(wide interface)
return (List)fruits.clone();
}
}
Gamer
import java.util.*;
public class Gamer {
private int money; // 所持金錢
private List fruits = new ArrayList(); // 獲得的水果
private Random random = new Random(); // 隨機數生成器
private static String[] fruitsname = { // 表示水果種類的數組
"蘋果", "葡萄", "香蕉", "橘子",
};
public Gamer(int money) { // 構造函數
this.money = money;
}
public int getMoney() { // 獲取當前所持金錢
return money;
}
public void bet() { // 投擲骰子進行遊戲
int dice = random.nextInt(6) + 1; // 擲骰子
if (dice == 1) { // 骰子結果爲1…增加所持金錢
money += 100;
System.out.println("所持金錢增加了。");
} else if (dice == 2) { // 骰子結果爲2…所持金錢減半
money /= 2;
System.out.println("所持金錢減半了。");
} else if (dice == 6) { // 骰子結果爲6…獲得水果
String f = getFruit();
System.out.println("獲得了水果(" + f + ")。");
fruits.add(f);
} else { // 骰子結果爲3、4、5則什麼都不會發生
System.out.println("什麼都沒有發生。");
}
}
public Memento createMemento() { // 拍攝快照
Memento m = new Memento(money);
Iterator it = fruits.iterator();
while (it.hasNext()) {
String f = (String)it.next();
if (f.startsWith("好吃的")) { // 只保存好吃的水果
m.addFruit(f);
}
}
return m;
}
public void restoreMemento(Memento memento) { // 撤銷
this.money = memento.money;
this.fruits = memento.getFruits();
}
@Override
public String toString() { // 用字符串表示主人公狀態
return "[money = " + money + ", fruits = " + fruits + "]";
}
private String getFruit() { // 獲得一個水果
String prefix = "";
if (random.nextBoolean()) {
prefix = "好吃的";
}
return prefix + fruitsname[random.nextInt(fruitsname.length)];
}
}
Main
public class Main {
public static void main(String[] args) {
Gamer gamer = new Gamer(100); // 最初的所持金錢數爲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() + "元。");
// 決定如何處理Memento
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) {
}
System.out.println("");
}
}
}
示例程序時序圖
Memento 模式中登場的角色
1.Originator(生成者)
Originator角色會在保存自己的最新狀態時生成Memento角色。當把以前保存的Memento角色傳遞給Originator角色時,它會將自己恢復至生成該Memento角色時的狀態。在示例程序中,由Gamer類扮演此角色。
2.Memento(紀念品)
Memento角色會將Originator角色的內部信息整合在一起。在Memento角色中雖然保存了Originator角色的信息,但它不會向外公佈這些信息。
Memento 角色有以下兩種接口(API)。
- wide interface——寬接口(API)
Memento角色提供的“寬接口(API)”是指所有用於獲取恢復對象狀態信息的方法的集合。由於寬接口(API)會暴露所有Memento角色的內部信息,因此能夠使用寬接口(API)的只有Originator角色。 - narrow interface——窄接口(API)
Memento角色爲外部的Caretaker角色提供了“窄接口(API)”。可以通過窄接口(API)獲取的Memento角色的內部信息非常有限,因此可以有效地防止信息泄漏。
通過對外提供以上兩種接口(API),可以有效地防止對象的封裝性被破壞。
在示例程序中,由Memento類扮演此角色。
Originator角色和Memento角色之間有着非常緊密的聯繫。
3.Caretaker(負責人)
當Caretaker角色想要保存當前的Originator角色的狀態時,會通知Originator角色。Originator角色在接收到通知後會生成Memento角色的實例並將其返回給Caretaker角色。由於以後可能會用Memento實例來將Originator恢復至原來的狀態,因此Caretaker角色會一直保存Memento實例。在示例程序中,由Main類扮演此角色。
不過,Caretaker角色只能使用Memento角色兩種接口(API)中的窄接口(API),也就是說它無法訪問Memento角色內部的所有信息。它只是將Originator角色生成的Memento角色當作一個黑盒子保存起來。
雖然Originator角色和Memento角色之間是強關聯關係,但Caretaker角色和Memento角色之間是弱關聯關係。Memento角色對Caretaker角色隱藏了自身的內部信息。
接口(API)寬窄的含義補充
“窄”的意思是指外部可以操作的類的內部的內容很少。“寬”則與之相反。