1. 備忘錄模式
1.1 簡(jiǎn)介
??備忘錄模式是一種軟件設(shè)計(jì)模式,它提供一種能將一個(gè)對(duì)象恢復(fù)到舊狀態(tài)的能力(回滾式的撤銷(xiāo)操作)。備忘錄模式又叫做快照模式(Snapshot Pattern)或Token模式婆誓,是對(duì)象的行為模式。Memento Pattern在不破壞封閉的前提下也颤,捕獲并外部化一個(gè)對(duì)象的內(nèi)部狀態(tài)洋幻,這樣之后就可以將該對(duì)象恢復(fù)到保存時(shí)的狀態(tài)。
??在應(yīng)用軟件的開(kāi)發(fā)過(guò)程中翅娶,很多時(shí)候我們都需要記錄一個(gè)對(duì)象的內(nèi)部狀態(tài)文留。在具體實(shí)現(xiàn)過(guò)程中,為了允許用戶(hù)取消不確定的操作或從錯(cuò)誤中恢復(fù)過(guò)來(lái)竭沫,需要實(shí)現(xiàn)備份點(diǎn)和撤銷(xiāo)機(jī)制厂庇,而要實(shí)現(xiàn)這些機(jī)制,必須事先將狀態(tài)信息保存在某處输吏,這樣才能將對(duì)象恢復(fù)到它們?cè)鹊臓顟B(tài)。備忘錄模式是一種給我們的軟件提供后悔藥的機(jī)制替蛉,通過(guò)它可以使系統(tǒng)恢復(fù)到某一特定的歷史狀態(tài)贯溅。
??備忘錄模式通過(guò)三個(gè)對(duì)象來(lái)實(shí)現(xiàn):originator、caretaker和memento躲查。originator是一些擁有內(nèi)部狀態(tài)的對(duì)象它浅。caretaker將在originator之上進(jìn)行一些處理,但是同時(shí)希望能夠撤銷(xiāo)之前的處理操作镣煮。caretaker首先向originator請(qǐng)求一個(gè)memento對(duì)象姐霍,然后它將進(jìn)行它所要進(jìn)行的任何操作。為了回滾回進(jìn)行這些操作的狀態(tài)典唇,caretaker將返回memento對(duì)象給originator镊折。memento對(duì)象是一種不透明對(duì)象(caretaker不能也不應(yīng)該對(duì)其改變)。使用這種模式的時(shí)候介衔,需要注意的是originator可能改變其他對(duì)象或資源的狀態(tài)恨胚,備忘錄模式只對(duì)單一對(duì)象起作用。
1.2 Memento Pattern的uml
Memento Pattern的角色:
Memento:
備忘錄炎咖,用于存儲(chǔ)originator對(duì)象的內(nèi)部狀態(tài)赃泡。memento對(duì)象應(yīng)該根據(jù)originator的實(shí)際需要,存儲(chǔ)必要的originator對(duì)象的內(nèi)部狀態(tài)乘盼。它能夠保護(hù)對(duì)狀態(tài)的訪問(wèn)升熊。memento實(shí)際上擁有兩個(gè)接口。caretaker對(duì)象只能訪問(wèn)memento對(duì)象的窄接口绸栅,即它只能夠把memento對(duì)象傳遞給其他對(duì)象级野。相反,originator對(duì)象可以訪問(wèn)memento的寬接口阴幌,即獲取所有能恢復(fù)到舊狀態(tài)的必要數(shù)據(jù)勺阐。理想情況下卷中,只有創(chuàng)建memento的oiginator對(duì)象才有權(quán)限訪問(wèn)memento對(duì)象的內(nèi)部狀態(tài)信息.Originator:
發(fā)起人。創(chuàng)建一個(gè)memento對(duì)象來(lái)存儲(chǔ)它當(dāng)前內(nèi)部狀態(tài)的一個(gè)快照渊抽,這些內(nèi)部信息將保存在memento對(duì)象之中蟆豫。Caretaker:
管理者,負(fù)責(zé)memento對(duì)象的安全存儲(chǔ)懒闷,但從不對(duì)memento對(duì)象的內(nèi)容進(jìn)行操作或檢查十减。
2. Memento Pattern的示例
??我們以游戲?yàn)槭纠螒蚪巧珦碛猩岛徒?jīng)驗(yàn)值愤估,每一次打怪升級(jí)都需要消耗生命值和經(jīng)驗(yàn)值帮辟,冷卻足夠時(shí)間后都會(huì)恢復(fù)。
** GamePlayer:**
// Originator
public class GamePlayer {
// 遊戲角色的生命值
private int mHp;
// 遊戲角色的經(jīng)驗(yàn)值
private int mExp;
public GamePlayer(int hp, int exp)
{
mHp = hp;
mExp = exp;
}
public GameMemento saveToMemento()
{
return new GameMemento(mHp, mExp);
}
public void restoreFromMemento(GameMemento memento)
{
mHp = memento.getGameHp();
mExp = memento.getGameExp();
}
public void play(int hp, int exp)
{
mHp = mHp - hp;
mExp = mExp + exp;
}
}
** GameMemento:**
// Memento
public class GameMemento {
// 假設(shè)只有這兩個(gè)資料要保留
private int mGameHp;
private int mGameExp;
public GameMemento(int hp, int exp)
{
mGameHp = hp;
mGameExp = exp;
}
public int getGameHp()
{
return mGameHp;
}
public int getGameExp()
{
return mGameExp;
}
}
** Caretaker:**
// Caretaker
public class GameCaretaker {
// 保留要處理的資料玩焰。
// 這邊只是範(fàn)例由驹,所以 Caretaker
// 只能處理一個(gè) Memento。
// 實(shí)務(wù)上當(dāng)然可以用更複雜的結(jié)構(gòu)來(lái)
// 處理多個(gè) Memento昔园,如 ArrayList蔓榄。
private GameMemento mMemento;
public GameMemento getMemento()
{
return mMemento;
}
public void setMemento(GameMemento memento)
{
mMemento = memento;
}
}
** 客戶(hù)端調(diào)用示例:**
public static void main(String[] args)
{
// 創(chuàng)造一個(gè)遊戲角色
GamePlayer player = new GamePlayer(100, 0);
// 先存?zhèn)€檔
GameCaretaker caretaker = new GameCaretaker();
caretaker.setMemento(player.seveToMemento());
// 不小心死掉啦
player.play(-100, 10);
// 重新讀取存檔,又是一尾活龍
player.restoreFromMemento(caretaker.getMemento());
}
3. 總結(jié)
備忘錄模式的優(yōu)點(diǎn):
- 提供了一種狀態(tài)恢復(fù)的實(shí)現(xiàn)機(jī)制默刚,使得用戶(hù)可以方便地回到一個(gè)特定的歷史步驟甥郑,當(dāng)新的狀態(tài)無(wú)效或者存在問(wèn)題時(shí),可以使用先前存儲(chǔ)起來(lái)的備忘錄將狀態(tài)復(fù)原荤西。
- 實(shí)現(xiàn)了信息的封裝澜搅,一個(gè)備忘錄對(duì)象是一種原發(fā)器對(duì)象的表示,不會(huì)被其他代碼改動(dòng)邪锌,這種模式簡(jiǎn)化了原發(fā)器對(duì)象勉躺,備忘錄只保存原發(fā)器的狀態(tài),采用堆棧來(lái)存儲(chǔ)備忘錄對(duì)象可以實(shí)現(xiàn)多次撤銷(xiāo)操作觅丰,可以通過(guò)在負(fù)責(zé)人中定義集合對(duì)象來(lái)存儲(chǔ)多個(gè)備忘錄赂蕴。
備忘錄模式的缺點(diǎn):
- 資源消耗過(guò)大,如果類(lèi)的成員變量太多舶胀,就不可避免占用大量的內(nèi)存概说,而且每保存一次對(duì)象的狀態(tài)都需要消耗內(nèi)存資源,如果知道這一點(diǎn)大家就容易理解為什么一些提供了撤銷(xiāo)功能的軟件在運(yùn)行時(shí)所需的內(nèi)存和硬盤(pán)空間比較大了嚣伐。
備忘錄模式適用環(huán)境:
- 保存一個(gè)對(duì)象在某一個(gè)時(shí)刻的狀態(tài)或部分狀態(tài)糖赔,這樣以后需要時(shí)它能夠恢復(fù)到先前的狀態(tài)。
- 如果用一個(gè)接口來(lái)讓其他對(duì)象得到這些狀態(tài)轩端,將會(huì)暴露對(duì)象的實(shí)現(xiàn)細(xì)節(jié)并破壞對(duì)象的封裝性放典,一個(gè)對(duì)象不希望外界直接訪問(wèn)其內(nèi)部狀態(tài),通過(guò)負(fù)責(zé)人可以間接訪問(wèn)其內(nèi)部狀態(tài)
備忘錄模式應(yīng)用:
- 幾乎所有的文字或者圖像編輯軟件都提供了撤銷(xiāo)(Ctrl+Z)的功能,即撤銷(xiāo)操作奋构,但是當(dāng)軟件關(guān)閉再打開(kāi)時(shí)不能再進(jìn)行撤銷(xiāo)操作壳影,也就是說(shuō)不能再回到關(guān)閉軟件前的狀態(tài),實(shí)際上這中間就使用到了備忘錄模式弥臼,在編輯文件的同時(shí)可以保存一些內(nèi)部狀態(tài)宴咧,這些狀態(tài)在軟件關(guān)閉時(shí)從內(nèi)存銷(xiāo)毀,當(dāng)然這些狀態(tài)的保存也不是無(wú)限的径缅,很多軟件只提供有限次的撤銷(xiāo)操作掺栅。
- 數(shù)據(jù)庫(kù)管理系統(tǒng)DBMS所提供的事務(wù)管理應(yīng)用了備忘錄模式,當(dāng)數(shù)據(jù)庫(kù)某事務(wù)中一條數(shù)據(jù)操作語(yǔ)句執(zhí)行失敗時(shí)纳猪,整個(gè)事務(wù)將進(jìn)行回滾操作氧卧,系統(tǒng)回到事務(wù)執(zhí)行之前的狀態(tài)。
備忘錄模式擴(kuò)展:
備忘錄的封裝性
- 為了確保備忘錄的封裝性氏堤,除了原發(fā)器外沙绝,其他類(lèi)是不能也不應(yīng)該訪問(wèn)備忘錄類(lèi)的,在實(shí)際開(kāi)發(fā)中鼠锈,原發(fā)器與備忘錄之間的關(guān)系是非常特殊的宿饱,它們要分享信息而不讓其他類(lèi)知道,實(shí)現(xiàn)的方法因編程語(yǔ)言的不同而不同脚祟。
- C++可以用friend關(guān)鍵字,使原發(fā)器類(lèi)和備忘錄類(lèi)成為友元類(lèi)强饮,互相之間可以訪問(wèn)對(duì)象的一些私有的屬性由桌;
- 在Java語(yǔ)言中可以將兩個(gè)類(lèi)放在一個(gè)包中,使它們之間滿(mǎn)足默認(rèn)的包內(nèi)可見(jiàn)性邮丰,也可以將備忘錄類(lèi)作為原發(fā)器類(lèi)的內(nèi)部類(lèi)行您,使得只有原發(fā)器才可以訪問(wèn)備忘錄中的數(shù)據(jù),其他對(duì)象都無(wú)法使用備忘錄中的數(shù)據(jù)剪廉。
多備份實(shí)現(xiàn) - 在負(fù)責(zé)人中定義一個(gè)集合對(duì)象來(lái)存儲(chǔ)多個(gè)狀態(tài)娃循,而且可以方便地返回到某一歷史狀態(tài)。
- 在備份對(duì)象時(shí)可以做一些記號(hào)斗蒋,這些記號(hào)稱(chēng)為檢查點(diǎn)(Check Point)捌斧。在使用HashMap等實(shí)現(xiàn)時(shí)可以使用Key來(lái)設(shè)置檢查點(diǎn)。