備忘錄模式

簡介

Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.
在不破壞封裝的前提下肉盹,捕獲一個對象的內部狀態(tài)沉填,并在對象之外保存這個狀態(tài)之拨。這樣以后就可將該對象恢復到原先保存的狀態(tài)央勒。

備忘錄模式(Memento Pattern)又稱為 快照模式(Snapshot Pattern)Token模式

在軟件系統(tǒng)中葱跋,備忘錄模式 為我們提供一種 “后悔藥” 的機制持寄,它通過存儲系統(tǒng)各個歷史狀態(tài)的快照,使得我們可以在任一時刻將系統(tǒng)回滾到某一個歷史狀態(tài)年局。

對于 備忘錄模式 來說际看,比較貼切的現(xiàn)實場景應該是游戲的存檔功能,通過將游戲當前進度存儲到本地文件系統(tǒng)或數(shù)據(jù)庫中矢否,使得下次繼續(xù)游戲時仲闽,玩家可以從之前的位置繼續(xù)進行。

備忘錄模式 本質:從發(fā)起人實體類(Originator)隔離存儲功能僵朗,降低實體類的職責赖欣。同時由于存儲信息(Memento)獨立屑彻,且存儲信息的實體交由管理類(Caretaker)管理,則可以通過為管理類擴展額外的功能對存儲信息進行擴展操作(比如增加歷史快照功能···)顶吮。

主要解決

存儲實體(Originator)狀態(tài)社牲,存儲歷史快照,回滾歷史狀態(tài)悴了。

優(yōu)缺點

優(yōu)點

  • 簡化發(fā)起人實體類(Originator)職責搏恤,隔離狀態(tài)存儲與獲取,實現(xiàn)了信息的封裝湃交,客戶端無需關心狀態(tài)的保存細節(jié)熟空;
  • 提供狀態(tài)回滾功能;

缺點

  • 消耗資源:如果需要保存的狀態(tài)過多時搞莺,每一次保存都會消耗很多內存息罗;

使用場景

  • 需要保存歷史快照的場景;
  • 希望在對象之外保存狀態(tài)才沧,且除了自己其他類對象無法訪問狀態(tài)保存具體內容迈喉;

模式講解

首先來看下 備忘錄模式 的通用 UML 類圖:

備忘錄模式

從 UML 類圖中,我們可以看到温圆,備忘錄模式 主要包含三種角色:

  • 發(fā)起人角色(Originator):負責創(chuàng)建一個備忘錄挨摸,記錄自身需要保存的狀態(tài);具備狀態(tài)回滾功能岁歉;
  • 備忘錄角色(Memento):用于存儲 Originator 的內部狀態(tài)油坝,且可以防止 Originator 以外的對象進行訪問;
  • 備忘錄管理員角色(Caretaker):負責存儲刨裆,提供,管理備忘錄(Memento)彬檀,無法對備忘錄內容進行操作和訪問帆啃;

以下是 備忘錄模式 的通用代碼:

class Client {
    public static void main(String[] args) {
        // 來一個發(fā)起人
        Originator originator = new Originator();
        // 來一個備忘錄管理員
        Caretaker caretaker = new Caretaker();
        // 管理員存儲發(fā)起人的備忘錄
        caretaker.storeMemento(originator.createMemento());
        // 發(fā)起人從管理員獲取備忘錄進行回滾
        originator.restoreMemento(caretaker.getMemento());

    }

    // 備忘錄 
    static class Memento {
        private String state;

        public Memento(String state){
            this.state = state;
        }

        public String getState() {
            return this.state;
        }

        public void setState(String state) {
            this.state = state;
        }
    }

    // 備忘錄管理員 
    static class Caretaker {
        // 備忘錄對象
        private Memento memento;

        public Memento getMemento() {
            return this.memento;
        }

        public void storeMemento(Memento memento) {
            this.memento = memento;
        }

    }

    // 發(fā)起人
    static class Originator {
        // 內部狀態(tài)
        private String state;

        public String getState() {
            return this.state;
        }

        public void setState(String state) {
            this.state = state;
        }

        // 創(chuàng)建一個備忘錄
        public Memento createMemento() {
            return new Memento(this.state);
        }

        // 從備忘錄恢復
        public void restoreMemento(Memento memento) {
            this.setState(memento.getState());
        }
    }
}

備忘錄模式 要求備忘錄(Memento)只對發(fā)起人(Originator)內容可見,對其他對象(Caretaker)內容不可見窍帝;但是在上面代碼中努潘,備忘錄管理員(Caretaker)其實是可以通過備忘錄(Memento)提供的相關方法(getStatesetState)獲取和修改內部狀態(tài)坤学,這違背了 備忘錄模式 的要求疯坤,可能造成備忘錄內部狀態(tài)無意間被其他對象修改,導致發(fā)起人狀態(tài)恢復錯誤深浮,系統(tǒng)穩(wěn)定性下降压怠。

修復上述問題的代碼如下所示:

interface IMemento {
}

static class Caretaker {
    // 備忘錄對象
    private IMemento memento;

    public IMemento getMemento() {
        return this.memento;
    }

    public void storeMemento(IMemento memento) {
        this.memento = memento;
    }

}

static class Originator {
    // 內部狀態(tài)
    private String state;
    ...
    ...
    // 從備忘錄恢復
    public void restoreMemento(IMemento memento) {
        this.state = ((Memento) memento).state;
    }

    private static class Memento implements IMemento {
        private String state;

        private Memento(String state) {
            this.state = state;
        }
    }
}

從上面的代碼中我們可以看到:我們把Memento作為Originator的私有靜態(tài)內部類,這樣就滿足了OriginatorMemento具有寬訪問權限飞苇,但是直接這樣做菌瘫,其他類(Caretaker)是無法訪問Memento的蜗顽,因此在最上面我們通過定義一個空接口IMemento,然后讓Memento實現(xiàn)IMemento雨让,變相將Memento擴展為公有IMemento雇盖,這樣就實現(xiàn)了其他類(Caretaker)對Memento具備窄訪問權限。這種形式才算是嚴格滿足 備忘錄模式 的設計要求栖忠。

舉個例子

例子:假設現(xiàn)有一個游戲崔挖,該游戲規(guī)定,對于之前通關的關卡庵寞,可以隨時跳回到任一關卡繼續(xù)游戲狸相。也就是說,如果你現(xiàn)在已經在第30關卡皇帮,那么30關卡之前的任一一關卷哩,你可以隨時切回去玩。請用代碼實現(xiàn)上述游戲邏輯属拾。

分析:對于已通過的關卡将谊,可以隨時切換回去玩,那么系統(tǒng)肯定是對已通過的關卡做了備份渐白,對于通關的每一關卡的相關內容都進行了存儲(為了簡單尊浓,我們只認為對關卡名字,關卡通關時間做了備份)纯衍,那么我們只需實現(xiàn)存儲功能與回滾功能即可栋齿。

具體代碼如下:

class Client {
    public static void main(String[] args) {
        GameCaretaker caretaker = new GameCaretaker();
        Game game = new Game();
        System.out.println(game);
        caretaker.addSnapshot(game.createGameInfo());

        game.doneChapter("002", 20);
        System.out.println(game);
        caretaker.addSnapshot(game.createGameInfo());

        game.doneChapter("003", 30);
        System.out.println(game);
        caretaker.addSnapshot(game.createGameInfo());

        game.restore(caretaker.getSnapshot("002"));
        System.out.println("rollback to chapter 002");
        System.out.println(game);
    }

    // 接口:IMemento
    interface IGameInfo {
        // 返回關卡名稱
        String name();
    }

    // Caretaker
    static class GameCaretaker {
        private Map<String, IGameInfo> mGameSnapshots = new HashMap<>();

        public void addSnapshot(IGameInfo snapshot) {
            this.mGameSnapshots.put(snapshot.name(), snapshot);
        }

        public IGameInfo getSnapshot(String name) {
            return this.mGameSnapshots.getOrDefault(name, null);
        }
    }

    // Originator
    static class Game {
        // 關卡時間
        private String mName;
        // 通關時間
        private int mCost;

        public Game() {
            this.mName = "001";
            this.mCost = 10;
        }

        public Game(String name, int cost) {
            this.mName = name;
            this.mCost = cost;
        }

        public void doneChapter(String name, int cost) {
            this.setName(name);
            this.setCost(cost);
        }

        private void setName(String name) {
            this.mName = name;
        }

        private void setCost(int cost) {
            this.mCost = cost;
        }

        public IGameInfo createGameInfo() {
            return new GameInfoStore(this.mName, this.mCost);
        }

        public void restore(IGameInfo info) {
            if (info == null) {
                throw new IllegalArgumentException("Game Snapshot is empty!!");
            }
            GameInfoStore game = (GameInfoStore) info;
            this.mName = game.name;
            this.mCost = game.cost;
        }

        @Override
        public String toString() {
            return String.format("Game[%s] cost you: %d mins", this.mName, this.mCost);
        }

        // Memento
        private static class GameInfoStore implements IGameInfo {
            private String name;
            private int cost;

            private GameInfoStore(String name, int cost) {
                this.name = name;
                this.cost = cost;
            }

            @Override
            public String name() {
                return this.name;
            }
        }
    }
}

我們通過為GameCaretaker添加一個集合記錄游戲關卡各個記錄,使整個系統(tǒng)具備回滾功能襟诸。

運行結果如下:

Game[001] cost you: 10 mins
Game[002] cost you: 20 mins
Game[003] cost you: 30 mins
rollback to chapter 002
Game[002] cost you: 20 mins
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末瓦堵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子歌亲,更是在濱河造成了極大的恐慌菇用,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陷揪,死亡現(xiàn)場離奇詭異惋鸥,居然都是意外死亡,警方通過查閱死者的電腦和手機悍缠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門卦绣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人飞蚓,你說我怎么就攤上這事滤港。” “怎么了趴拧?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵蜗搔,是天一觀的道長劲藐。 經常有香客問我,道長樟凄,這世上最難降的妖魔是什么聘芜? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮缝龄,結果婚禮上汰现,老公的妹妹穿的比我還像新娘。我一直安慰自己叔壤,他們只是感情好瞎饲,可當我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著炼绘,像睡著了一般嗅战。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俺亮,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天驮捍,我揣著相機與錄音,去河邊找鬼脚曾。 笑死东且,一個胖子當著我的面吹牛,可吹牛的內容都是我干的本讥。 我是一名探鬼主播珊泳,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拷沸!你這毒婦竟也來了色查?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤撞芍,失蹤者是張志新(化名)和其女友劉穎综慎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勤庐,經...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年好港,在試婚紗的時候發(fā)現(xiàn)自己被綠了愉镰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡钧汹,死狀恐怖丈探,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情拔莱,我是刑警寧澤碗降,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布隘竭,位于F島的核電站,受9級特大地震影響讼渊,放射性物質發(fā)生泄漏动看。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一爪幻、第九天 我趴在偏房一處隱蔽的房頂上張望菱皆。 院中可真熱鬧,春花似錦挨稿、人聲如沸仇轻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽篷店。三九已至,卻和暖如春臭家,著一層夾襖步出監(jiān)牢的瞬間疲陕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工侣监, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鸭轮,地道東北人。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓橄霉,卻偏偏與公主長得像窃爷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子姓蜂,可洞房花燭夜當晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內容