16弟跑、備忘錄模式(Memento Pattern)

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 uml.png

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)。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泉沾,一起剝皮案震驚了整個(gè)濱河市捞蚂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌跷究,老刑警劉巖姓迅,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡丁存,警方通過(guò)查閱死者的電腦和手機(jī)肩杈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)解寝,“玉大人扩然,你說(shuō)我怎么就攤上這事”嗲穑” “怎么了与学?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)嘉抓。 經(jīng)常有香客問(wèn)我索守,道長(zhǎng),這世上最難降的妖魔是什么抑片? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任卵佛,我火速辦了婚禮,結(jié)果婚禮上敞斋,老公的妹妹穿的比我還像新娘截汪。我一直安慰自己,他們只是感情好植捎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布衙解。 她就那樣靜靜地躺著,像睡著了一般焰枢。 火紅的嫁衣襯著肌膚如雪蚓峦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天济锄,我揣著相機(jī)與錄音暑椰,去河邊找鬼。 笑死荐绝,一個(gè)胖子當(dāng)著我的面吹牛一汽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播低滩,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼召夹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了恕沫?” 一聲冷哼從身側(cè)響起戳鹅,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昏兆,沒(méi)想到半個(gè)月后枫虏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體妇穴,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年隶债,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腾它。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡死讹,死狀恐怖瞒滴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赞警,我是刑警寧澤妓忍,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站愧旦,受9級(jí)特大地震影響世剖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笤虫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一旁瘫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧琼蚯,春花似錦酬凳、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至峦睡,卻和暖如春翎苫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赐俗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弊知,地道東北人阻逮。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像秩彤,于是被迫代替她去往敵國(guó)和親叔扼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354