Memento模式(備忘錄設計模式)
阿新 • • 發佈:2018-12-18
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方法
*/
/**
* 儲存
*/
/**
* 恢復
*/
}