16、備忘錄模式(Memento Pattern)

1. 備忘錄模式

1.1 簡介

??備忘錄模式是一種軟件設(shè)計模式,它提供一種能將一個對象恢復(fù)到舊狀態(tài)的能力(回滾式的撤銷操作)。備忘錄模式又叫做快照模式(Snapshot Pattern)或Token模式,是對象的行為模式。Memento Pattern在不破壞封閉的前提下,捕獲并外部化一個對象的內(nèi)部狀態(tài),這樣之后就可以將該對象恢復(fù)到保存時的狀態(tài)。

??在應(yīng)用軟件的開發(fā)過程中,很多時候我們都需要記錄一個對象的內(nèi)部狀態(tài)。在具體實現(xiàn)過程中,為了允許用戶取消不確定的操作或從錯誤中恢復(fù)過來,需要實現(xiàn)備份點和撤銷機(jī)制,而要實現(xiàn)這些機(jī)制,必須事先將狀態(tài)信息保存在某處,這樣才能將對象恢復(fù)到它們原先的狀態(tài)。備忘錄模式是一種給我們的軟件提供后悔藥的機(jī)制,通過它可以使系統(tǒng)恢復(fù)到某一特定的歷史狀態(tài)。

??備忘錄模式通過三個對象來實現(xiàn):originator、caretaker和memento。originator是一些擁有內(nèi)部狀態(tài)的對象。caretaker將在originator之上進(jìn)行一些處理,但是同時希望能夠撤銷之前的處理操作。caretaker首先向originator請求一個memento對象,然后它將進(jìn)行它所要進(jìn)行的任何操作。為了回滾回進(jìn)行這些操作的狀態(tài),caretaker將返回memento對象給originator。memento對象是一種不透明對象(caretaker不能也不應(yīng)該對其改變)。使用這種模式的時候,需要注意的是originator可能改變其他對象或資源的狀態(tài),備忘錄模式只對單一對象起作用。

1.2 Memento Pattern的uml

Memento uml.png

Memento Pattern的角色:

  • Memento:
    備忘錄,用于存儲originator對象的內(nèi)部狀態(tài)。memento對象應(yīng)該根據(jù)originator的實際需要,存儲必要的originator對象的內(nèi)部狀態(tài)。它能夠保護(hù)對狀態(tài)的訪問。memento實際上擁有兩個接口。caretaker對象只能訪問memento對象的窄接口,即它只能夠把memento對象傳遞給其他對象。相反,originator對象可以訪問memento的寬接口,即獲取所有能恢復(fù)到舊狀態(tài)的必要數(shù)據(jù)。理想情況下,只有創(chuàng)建memento的oiginator對象才有權(quán)限訪問memento對象的內(nèi)部狀態(tài)信息.

  • Originator:
    發(fā)起人。創(chuàng)建一個memento對象來存儲它當(dāng)前內(nèi)部狀態(tài)的一個快照,這些內(nèi)部信息將保存在memento對象之中。

  • Caretaker:
    管理者,負(fù)責(zé)memento對象的安全存儲,但從不對memento對象的內(nèi)容進(jìn)行操作或檢查。

2. Memento Pattern的示例

??我們以游戲為示例,游戲角色擁有生命值和經(jīng)驗值,每一次打怪升級都需要消耗生命值和經(jīng)驗值,冷卻足夠時間后都會恢復(fù)。

** GamePlayer:**

// Originator
public class GamePlayer {

    // 遊戲角色的生命值
    private int mHp;

    // 遊戲角色的經(jīng)驗值
    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è)只有這兩個資料要保留
    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
    // 只能處理一個 Memento。
    // 實務(wù)上當(dāng)然可以用更複雜的結(jié)構(gòu)來
    // 處理多個 Memento,如 ArrayList。
    private GameMemento mMemento;

    public GameMemento getMemento()
    {
        return mMemento;
    }

    public void setMemento(GameMemento memento)
    {
        mMemento = memento;
    }
}

** 客戶端調(diào)用示例:**

 public static void main(String[] args)
    {
        // 創(chuàng)造一個遊戲角色
        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)點:

  • 提供了一種狀態(tài)恢復(fù)的實現(xiàn)機(jī)制,使得用戶可以方便地回到一個特定的歷史步驟,當(dāng)新的狀態(tài)無效或者存在問題時,可以使用先前存儲起來的備忘錄將狀態(tài)復(fù)原。
  • 實現(xiàn)了信息的封裝,一個備忘錄對象是一種原發(fā)器對象的表示,不會被其他代碼改動,這種模式簡化了原發(fā)器對象,備忘錄只保存原發(fā)器的狀態(tài),采用堆棧來存儲備忘錄對象可以實現(xiàn)多次撤銷操作,可以通過在負(fù)責(zé)人中定義集合對象來存儲多個備忘錄。

備忘錄模式的缺點:

  • 資源消耗過大,如果類的成員變量太多,就不可避免占用大量的內(nèi)存,而且每保存一次對象的狀態(tài)都需要消耗內(nèi)存資源,如果知道這一點大家就容易理解為什么一些提供了撤銷功能的軟件在運(yùn)行時所需的內(nèi)存和硬盤空間比較大了。

備忘錄模式適用環(huán)境:

  • 保存一個對象在某一個時刻的狀態(tài)或部分狀態(tài),這樣以后需要時它能夠恢復(fù)到先前的狀態(tài)。
  • 如果用一個接口來讓其他對象得到這些狀態(tài),將會暴露對象的實現(xiàn)細(xì)節(jié)并破壞對象的封裝性,一個對象不希望外界直接訪問其內(nèi)部狀態(tài),通過負(fù)責(zé)人可以間接訪問其內(nèi)部狀態(tài)

備忘錄模式應(yīng)用:

  • 幾乎所有的文字或者圖像編輯軟件都提供了撤銷(Ctrl+Z)的功能,即撤銷操作,但是當(dāng)軟件關(guān)閉再打開時不能再進(jìn)行撤銷操作,也就是說不能再回到關(guān)閉軟件前的狀態(tài),實際上這中間就使用到了備忘錄模式,在編輯文件的同時可以保存一些內(nèi)部狀態(tài),這些狀態(tài)在軟件關(guān)閉時從內(nèi)存銷毀,當(dāng)然這些狀態(tài)的保存也不是無限的,很多軟件只提供有限次的撤銷操作。
  • 數(shù)據(jù)庫管理系統(tǒng)DBMS所提供的事務(wù)管理應(yīng)用了備忘錄模式,當(dāng)數(shù)據(jù)庫某事務(wù)中一條數(shù)據(jù)操作語句執(zhí)行失敗時,整個事務(wù)將進(jìn)行回滾操作,系統(tǒng)回到事務(wù)執(zhí)行之前的狀態(tài)。

備忘錄模式擴(kuò)展:
備忘錄的封裝性

  • 為了確保備忘錄的封裝性,除了原發(fā)器外,其他類是不能也不應(yīng)該訪問備忘錄類的,在實際開發(fā)中,原發(fā)器與備忘錄之間的關(guān)系是非常特殊的,它們要分享信息而不讓其他類知道,實現(xiàn)的方法因編程語言的不同而不同。
  • C++可以用friend關(guān)鍵字,使原發(fā)器類和備忘錄類成為友元類,互相之間可以訪問對象的一些私有的屬性;
  • 在Java語言中可以將兩個類放在一個包中,使它們之間滿足默認(rèn)的包內(nèi)可見性,也可以將備忘錄類作為原發(fā)器類的內(nèi)部類,使得只有原發(fā)器才可以訪問備忘錄中的數(shù)據(jù),其他對象都無法使用備忘錄中的數(shù)據(jù)。
    多備份實現(xiàn)
  • 在負(fù)責(zé)人中定義一個集合對象來存儲多個狀態(tài),而且可以方便地返回到某一歷史狀態(tài)。
  • 在備份對象時可以做一些記號,這些記號稱為檢查點(Check Point)。在使用HashMap等實現(xiàn)時可以使用Key來設(shè)置檢查點。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容