設計模式學習筆記——備忘錄(Memento)模式
@(設計模式)[設計模式, 備忘錄模式, memento]
基本介紹
備忘錄模式提供的基本功能是:保存對象狀態信息(快照)、撤銷、重做和歷史記錄。
備忘錄模式一般會提供兩種接口:寬接口和窄接口。通過寬接口可以獲取整個對象狀態,會暴露備忘錄對象的內部信息。通過窄接口,只能訪問有限的,開發者限制了的信息,可以有效的防止信息泄露。
備忘錄案例
類圖
實現代碼
Memento類
package com.pc.memento.example;
import java.util.ArrayList;
import java.util.List;
/**
* 備忘錄類
* Created by Switch on 2017/3/31.
*/
public class Memento {
/**
* 所持金錢
*/
private int money;
/**
* 獲得的水果
*/
private List<String> fruits;
/**
* 獲取當前所持金錢
*
* @return 所持金錢
*/
public int getMoney() {
return money;
}
/**
* 構造方法,初始化所持錢數
*
* @param money 初始化錢數
*/
Memento(int money) {
this.money = money;
this.fruits = new ArrayList<>();
}
/**
* 添加水果
*
* @param fruit 水果
*/
void addFruit(String fruit) {
fruits.add(fruit);
}
/**
* 添加當前所持的所有水果
*
* @return 水果列表
*/
List<String> getFruits() {
return fruits;
}
}
Gamer類
package com.pc.memento.example;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* 遊戲者類
* Created by Switch on 2017/3/31.
*/
public class Gamer {
/**
* 所持金錢
*/
private int money;
/**
* 獲得的水果
*/
private List<String> fruits = new ArrayList<>();
/**
* 隨機數生成器
*/
private Random random = new Random();
/**
* 表示水果種類的數組
*/
private static String[] fruitsname = {"蘋果", "葡萄", "香蕉", "橘子"};
/**
* 構造方法,初始化金錢數
*
* @param money 金錢數
*/
public Gamer(int money) {
this.money = money;
}
/**
* 獲取當前所持金錢數
*
* @return 金錢數
*/
public int getMoney() {
return money;
}
/**
* 投擲骰子進行遊戲
*/
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 = this.getFruit();
System.out.println("獲得了水果(" + f + ")。");
this.fruits.add(f);
} else {
System.out.println("什麼都沒有發生。");
}
}
/**
* 創建備忘錄
*
* @return 備忘錄對象
*/
public Memento createMemento() {
Memento m = new Memento(money);
Iterator<String> it = fruits.iterator();
while (it.hasNext()) {
String fruit = it.next();
if (fruit.startsWith("好喫的")) {
m.addFruit(fruit);
}
}
return m;
}
/**
* 撤銷到指定備忘
*
* @param memento 備忘錄對象
*/
public void restoreMemento(Memento memento) {
this.money = memento.getMoney();
this.fruits = memento.getFruits();
}
/**
* 獲得一個水果
*
* @return 水果
*/
private String getFruit() {
String prefix = "";
if (random.nextBoolean()) {
prefix = "好喫的";
}
return prefix + fruitsname[random.nextInt(fruitsname.length)];
}
@Override
public String toString() {
return "Gamer{ money=" + money + ", fruits=" + fruits + '}';
}
}
測試類
package com.pc.memento.example.test;
import com.pc.memento.example.Gamer;
import com.pc.memento.example.Memento;
import org.junit.Test;
/**
* Memento Tester.
*
* @author Switch
* @version 1.0
*/
public class MementoTest {
/**
* 測試備忘錄模式
*/
@Test
public void testMemento() {
Gamer gamer = new Gamer(100);
Memento memento = gamer.createMemento();
for (int i = 0; i < 20; 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(100);
} catch (InterruptedException e) {
}
System.out.println("");
}
}
}
運行結果
==== 0
當前狀態:Gamer{ money=100, fruits=[]}
什麼都沒有發生。
所持金錢爲100元。
==== 1
當前狀態:Gamer{ money=100, fruits=[]}
所持金錢減半了。
所持金錢爲50元。
==== 2
當前狀態:Gamer{ money=50, fruits=[]}
所持金錢減半了。
所持金錢爲25元。
(所持金錢減少了許多,因此將遊戲恢復至以前的狀態)
==== 3
當前狀態:Gamer{ money=100, fruits=[]}
獲得了水果(好喫的葡萄)。
所持金錢爲100元。
==== 4
當前狀態:Gamer{ money=100, fruits=[好喫的葡萄]}
所持金錢增加了。
所持金錢爲200元。
(所持金錢增加了許多,因此保存遊戲當前的狀態)
==== 5
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄]}
所持金錢減半了。
所持金錢爲100元。
==== 6
當前狀態:Gamer{ money=100, fruits=[好喫的葡萄]}
獲得了水果(葡萄)。
所持金錢爲100元。
==== 7
當前狀態:Gamer{ money=100, fruits=[好喫的葡萄, 葡萄]}
所持金錢增加了。
所持金錢爲200元。
==== 8
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄]}
所持金錢減半了。
所持金錢爲100元。
==== 9
當前狀態:Gamer{ money=100, fruits=[好喫的葡萄, 葡萄]}
所持金錢增加了。
所持金錢爲200元。
==== 10
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄]}
什麼都沒有發生。
所持金錢爲200元。
==== 11
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄]}
所持金錢增加了。
所持金錢爲300元。
(所持金錢增加了許多,因此保存遊戲當前的狀態)
==== 12
當前狀態:Gamer{ money=300, fruits=[好喫的葡萄, 葡萄]}
所持金錢增加了。
所持金錢爲400元。
(所持金錢增加了許多,因此保存遊戲當前的狀態)
==== 13
當前狀態:Gamer{ money=400, fruits=[好喫的葡萄, 葡萄]}
所持金錢減半了。
所持金錢爲200元。
==== 14
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄]}
獲得了水果(葡萄)。
所持金錢爲200元。
==== 15
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄, 葡萄]}
獲得了水果(好喫的橘子)。
所持金錢爲200元。
==== 16
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄, 葡萄, 好喫的橘子]}
什麼都沒有發生。
所持金錢爲200元。
==== 17
當前狀態:Gamer{ money=200, fruits=[好喫的葡萄, 葡萄, 葡萄, 好喫的橘子]}
所持金錢增加了。
所持金錢爲300元。
==== 18
當前狀態:Gamer{ money=300, fruits=[好喫的葡萄, 葡萄, 葡萄, 好喫的橘子]}
所持金錢減半了。
所持金錢爲150元。
(所持金錢減少了許多,因此將遊戲恢復至以前的狀態)
==== 19
當前狀態:Gamer{ money=400, fruits=[好喫的葡萄]}
什麼都沒有發生。
所持金錢爲400元。
備忘錄模式中的角色
Originator(生成者)
Originator
角色會在保存自己的最新狀態時生成Memento
角色。當把以前保存的Memento
角色傳遞給Originator
角色時,它會將自己恢復至生成該Memento
角色時的狀態。在案例中,由Gamer
類扮演此角色。
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
角色之間有着非常緊密的聯繫。
Caretaker(負責人)
當Caretaker
角色想要保存當前的Originator
角色的狀態時,會通知Originator
角色。Originator
角色在接收到通知後會生成Memento
角色的實例並將其返回給Caretaker
角色。由於以後可能會用Memento
實例來將Originator
恢復至原來的狀態,因此Caretaker
角色會一直保存Memento
實例。在案例中,由測試類扮演此角色。
不過,Caretaker
角色只能使用Memento
角色兩種接口(API
)中的窄接口(API
),也就是說它無法訪問Memento
角色內部的所有信息。它只是將Originator
角色生成的Memento
角色當作一個黑盒子保存起來。
雖然Originator
角色和Memento
角色之間是強關聯關係,但Caretaker
角色和Memento
角色之間是弱關聯關係。Memento
角色對Caretaker
角色隱藏了自身的內部信息。
類圖
GitHub:DesignPatternStudy
——————參考《圖解設計模式》