設計模式--備忘錄模式

目錄

本文的結構如下:

  • 引言
  • 什么是備忘錄模式
  • 模式的結構
  • 典型代碼
  • 代碼示例
  • 優(yōu)點和缺點
  • 適用環(huán)境
  • 模式應用

一、引言

晚上躺在被窩里括改,突兀的腻豌,腦海主動把往事翻出來倒帶重播,那些舊時光的畫面很清晰,就像投影片一樣投在雪白的墻面饲梭,回憶著乘盖,便思緒萬千,下意識發(fā)出一聲輕嘆憔涉,哎订框,看樣子要失眠了。

有很多遺憾兜叨,有很多舍不得穿扳,只是怎么也回不去啊,人生又不是一部重生小說国旷,夏洛也只出現在熒幕中矛物。

回不去的從前

所以說,好好敲代碼吧跪但,代碼敲錯了履羞,可以ctrl+z,代碼提交錯了屡久,可以git reset忆首。

在軟件開發(fā)中,很多時候需要記錄一個對象的內部狀態(tài)被环,目的就是為了允許用戶取消不確定或者錯誤的操作糙及,能夠恢復到原先的狀態(tài),使得有“后悔藥”可吃筛欢。備忘錄模式是一種給軟件提供后悔藥的機制浸锨,通過它可以使系統恢復到某一特定的歷史狀態(tài)。

二版姑、什么是備忘錄模式

備忘錄模式提供了一種狀態(tài)恢復的實現機制柱搜,使得用戶可以方便地回到一個特定的歷史步驟。

備忘錄模式定義如下:

備忘錄模式(Memento Pattern):在不破壞封裝的前提下漠酿,捕獲一個對象的內部狀態(tài)冯凹,并在該對象之外保存這個狀態(tài)谎亩,這樣可以在以后將對象恢復到原先保存的狀態(tài)炒嘲。它是一種對象行為型模式,其別名為Token匈庭。

三夫凸、模式的結構

備忘錄模式UML類圖如下:

UML類圖

備忘錄模式主要包含入下幾個角色:

  • Originator(原發(fā)器):它是一個普通類,可以創(chuàng)建一個備忘錄阱持,并儲存該類當前的一些內部狀態(tài)夭拌,也可以使用備忘錄來恢復其內部狀態(tài),一般將需要保存內部狀態(tài)的類設計為原發(fā)器。
  • Memento(備忘錄):存儲原發(fā)器的內部狀態(tài)鸽扁,根據原發(fā)器來決定保存哪些內部狀態(tài)蒜绽。備忘錄的設計一般可以參考原發(fā)器的設計,根據實際需要確定備忘錄類中的屬性桶现。需要注意的是躲雅,除了原發(fā)器本身與負責人類之外,備忘錄對象不能直接供其他類使用骡和。
  • Caretaker(負責人):負責人又稱為管理者相赁,它負責保存?zhèn)渫洠遣荒軐渫浀膬热葸M行操作或檢查慰于。在負責人類中可以存儲一個或多個備忘錄對象钮科,它只負責存儲對象,而不能修改對象婆赠,也無須知道對象的實現細節(jié)绵脯。

在備忘錄模式中,最重要的就是備忘錄Memento了休里。由于在備忘錄中存儲的是原發(fā)器的中間狀態(tài)桨嫁,因此需要防止原發(fā)器以外的其他對象訪問備忘錄,特別是不允許其他對象來修改備忘錄份帐。

為了不破壞備忘錄的封裝性璃吧,我們需要對備忘錄的訪問做些控制:

  • 對原發(fā)器:可以訪問備忘錄里的所有信息。
  • 對負責人:不可以訪問備忘錄里面的數據废境,但是他可以保存?zhèn)渫洸⑶铱梢詫渫泜鬟f給其他對象畜挨。
  • 其他對象:不可訪問也不可以保存,它只負責接收從負責人那里傳遞過來的備忘錄同時恢復原發(fā)器的狀態(tài)噩凹。

所以就備忘錄模式而言理想的情況就是只允許生成該備忘錄的那個原發(fā)器訪問備忘錄的內部狀態(tài)巴元。

四、典型代碼

在真實業(yè)務中驮宴,原發(fā)器類是一個具體的業(yè)務類逮刨,它包含一些用于存儲成員數據的屬性,原發(fā)器典型代碼如下:

public class Originator {
    private String state;

    public void restoreMemento(Memento m){
        this.state = m.getState();
    }

    public Memento createMemento(){
        return new Memento(state);
    }

    public String getState() {
        return state;
    }

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

    public String toString(){
        return "originator---" + state;
    }
}

對于備忘錄類Memento而言堵泽,它通常提供了與原發(fā)器相對應的屬性(可以是全部修己,也可以是部分)用于存儲原發(fā)器的狀態(tài)。

在設計備忘錄類時需要考慮其封裝性迎罗,除了Originator類睬愤,不允許其他類來調用備忘錄類Memento的構造函數與相關方法,如果不考慮封裝性纹安,允許其他類調用setState()等方法尤辱,將導致在備忘錄中保存的歷史狀態(tài)發(fā)生改變砂豌,通過撤銷操作所恢復的狀態(tài)就不再是真實的歷史狀態(tài),備忘錄模式也就失去了本身的意義光督。

在使用Java語言實現備忘錄模式時阳距,一般通過將Memento類與Originator類定義在同一個包(package)中來實現封裝,在Java語言中可使用默認訪問標識符來定義Memento類结借,即保證其包內可見娄涩。只有Originator類可以對Memento進行訪問,而限制了其他類對Memento的訪問映跟。在Memento中保存了Originator的state值蓄拣,如果Originator中的state值改變之后需撤銷,可以通過調用它的restoreMemento()方法進行恢復努隙。

典型代碼如下:

class Memento {
    private String state;

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

    public String getState() {
        return state;
    }

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

負責人類Caretaker用于保存?zhèn)渫泴ο笄蛐簦⑻峁ゞetMemento()方法用于向客戶端返回一個備忘錄對象,原發(fā)器通過使用這個備忘錄對象可以回到某個歷史狀態(tài)荸镊。負責人典型代碼如下:

public class Caretaker {
    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}
類關系

簡單測試一下:

public class MementoDemo {
    public static void main(String[] args) {
        Originator originator = new Originator();
        originator.setState("state1");
        System.out.println(originator);

        Caretaker caretaker = new Caretaker();
        caretaker.setMemento(originator.createMemento());
        originator.setState("state2");
        System.out.println(originator);

        originator.restoreMemento(caretaker.getMemento());
        System.out.println(originator);
    }
}

五咽斧、代碼示例

剛上大學那會迷上籃球,玩游戲也都和籃球相關躬存,大一暑假张惹,便安裝了2K 11,自建了一個大中鋒岭洲,然后修改器身高調到最高宛逗,力量調到最大,速度調到最快盾剩,投籃調到最好雷激,為得當然是刷數據了(捂臉)。記得有一場比賽告私,辛苦打了40分鐘屎暇,數據超好,比分落后2分驻粟,還剩最后1s根悼,有三分絕殺的機會,這個情況下當然是先存檔蜀撑,絕殺不中可以回檔挤巡,總會投中的。

這里就以此為例屯掖。

5.1玄柏、第一版

public class Game {
    private int ourScore;//我方分數
    private int oppositeScore;//對方分數
    private boolean end;

    public Game(int ourScore, int oppositeScore) {
        this.ourScore = ourScore;
        this.oppositeScore = oppositeScore;
    }

    /**
     * 我方投籃
     * @param goal
     * @param point
     */
    public void shoot(boolean goal, int point){
        if (end){
            System.out.println("比賽已經結束,接收現實少年");
        }else {
            if (goal){
                this.ourScore += point;
            }
        }
    }

    /**
     * 對方投籃
     * @param goal
     * @param point
     */
    public void autoShoot(boolean goal, int point){
        if (end){
            System.out.println("比賽已經結束贴铜,接收現實少年");
        }else {
            if (goal){
                this.oppositeScore += point;
            }
        }
    }

    /**
     * 回檔
     * @param record
     */
    public void restoreRecord(Record record){
        this.end = false;
        this.ourScore = record.getOurScore();
        this.oppositeScore = record.getOppositeScore();
    }

    /**
     * 存檔
     * @return
     */
    public Record saveRecord(){
        return new Record(ourScore, oppositeScore);
    }

    public void showGame(){
        System.out.println("我方得分:" + ourScore  + ",對方得分:" + oppositeScore);
        if (end){
            System.out.println((ourScore > oppositeScore ? ",我方獲勝" : ourScore == oppositeScore ? ",打平進入加時" : "绍坝,對方獲勝"));
        }
    }

    public int getOurScore() {
        return ourScore;
    }

    public int getOppositeScore() {
        return oppositeScore;
    }

    public void setOurScore(int ourScore) {
        this.ourScore = ourScore;
    }

    public void setOppositeScore(int oppositeScore) {
        this.oppositeScore = oppositeScore;
    }

    public boolean isEnd() {
        return end;
    }

    public void setEnd(boolean end) {
        this.end = end;
    }
}

存檔:

class Record {
    private int ourScore;//我方分數
    private int oppositeScore;//對方分數

    public Record(int ourScore, int oppositeScore) {
        this.ourScore = ourScore;
        this.oppositeScore = oppositeScore;
    }

    public void setOurScore(int ourScore) {
        this.ourScore = ourScore;
    }

    public void setOppositeScore(int oppositeScore) {
        this.oppositeScore = oppositeScore;
    }

    public int getOurScore() {
        return ourScore;
    }

    public int getOppositeScore() {
        return oppositeScore;
    }
}

GameCaketaker持有存檔:

public class GameCaretaker {
    private Record record;

    public Record getRecord() {
        return record;
    }

    public void setRecord(Record record) {
        this.record = record;
    }
}

測試:

public class MementoDemo {
    public static void main(String[] args) {
        GameCaretaker caretaker = new GameCaretaker();
        Game game = new Game(97, 99);
        //先存檔
        caretaker.setRecord(game.saveRecord());

        shoot(game, false, 3);

        //回檔
        game.restoreRecord(caretaker.getRecord());
        shoot(game,false, 3);

        //再回檔
        game.restoreRecord(caretaker.getRecord());
        shoot(game, true, 3);
    }

    private static void shoot(Game game, boolean goal, int point ){
        game.shoot(goal, point);
        game.setEnd(true);
        game.showGame();
    }
}

已經可以用了徘意,但是會發(fā)現這里只能回退一步,只能回到上一個最新的存檔轩褐,下面看看多步回退椎咧。

5.2、第二版

public class GameCaretaker {
    private Map<Integer, Record> records = new HashMap<Integer, Record>();

    public Record getRecords(Integer key) {
        if (records.containsKey(key)){
            return records.get(key);
        }else {
            throw new RuntimeException();
        }
    }

    public void setRecords(Integer key, Record record) {
        records.put(key, record);
    }

    public void showRecords(){
        System.out.println("有" + records.keySet().size() + "個存檔把介,如下:");
        for (Integer key: records.keySet()){
            System.out.println("存檔" + key);
        }
    }
}

測試:

public class MementoDemo {
    private static GameCaretaker caretaker = new GameCaretaker();
    private static Game game = new Game(98,100);
    private static Integer key = 1;
    public static void main(String[] args) {
        play(false, 2, false, 3);
        game.showGame();
        showRecords();

        System.out.println("================================================================");

        play(true, 2, true, 2);
        game.showGame();
        showRecords();

        System.out.println("================================================================");

        play(true, 3, true, 1);
        game.showGame();
        showRecords();

        System.out.println("================================================================");
        play(false, 2, true, 3);
        game.setEnd(true);
        game.showGame();

        System.out.println("================================================================");

        //回到存檔1
        System.out.println("回到存檔1");
        game.restoreRecord(caretaker.getRecords(1));
        play(false, 2, true, 3);
        game.setEnd(true);
        game.showGame();
    }

    private static void play(boolean goal1, int point1, boolean goal2, int point2){
        game.autoShoot(goal1, point1);
        game.shoot(goal2, point2);
        caretaker.setRecords(key++, game.saveRecord());
    }

    private static void showRecords(){
        caretaker.showRecords();
    }
}

六勤讽、優(yōu)點和缺點

6.1、優(yōu)點

備忘錄模式的主要優(yōu)點如下:

  • 它提供了一種狀態(tài)恢復的實現機制拗踢,使得用戶可以方便地回到一個特定的歷史步驟脚牍,當新的狀態(tài)無效或者存在問題時,可以使用暫時存儲起來的備忘錄將狀態(tài)復原巢墅。
  • 備忘錄實現了對信息的封裝诸狭,一個備忘錄對象是一種原發(fā)器對象狀態(tài)的表示,不會被其他代碼所改動君纫。備忘錄保存了原發(fā)器的狀態(tài)驯遇,采用列表、堆棧等集合來存儲備忘錄對象可以實現多次撤銷操作蓄髓。

6.2叉庐、缺點

備忘錄模式的主要缺點如下:

  • 資源消耗過大,如果需要保存的原發(fā)器類的成員變量太多会喝,就不可避免需要占用大量的存儲空間眨唬,每保存一次對象的狀態(tài)都需要消耗一定的系統資源。

七好乐、適用環(huán)境

備忘錄模式在很多軟件的使用過程中普遍存在匾竿,但是在應用軟件開發(fā)中,它的使用頻率并不太高蔚万,因為現在很多基于窗體和瀏覽器的應用軟件并沒有提供撤銷操作岭妖。

在以下情況下可以考慮使用備忘錄模式:

  • 需要保存一個對象在某一個時刻的狀態(tài)或部分狀態(tài)。
  • 防止外界對象破壞一個對象歷史狀態(tài)的封裝性反璃,避免將對象歷史狀態(tài)的實現細節(jié)暴露給外界對象昵慌。

八、模式應用

在一些字處理軟件淮蜈、圖像編輯軟件斋攀、數據庫管理系統等軟件中備忘錄模式都得到了很好的應用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末梧田,一起剝皮案震驚了整個濱河市淳蔼,隨后出現的幾起案子侧蘸,更是在濱河造成了極大的恐慌,老刑警劉巖鹉梨,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讳癌,死亡現場離奇詭異,居然都是意外死亡存皂,警方通過查閱死者的電腦和手機晌坤,發(fā)現死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旦袋,“玉大人骤菠,你說我怎么就攤上這事“淘校” “怎么了商乎?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胰柑。 經常有香客問我截亦,道長,這世上最難降的妖魔是什么柬讨? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任崩瓤,我火速辦了婚禮,結果婚禮上踩官,老公的妹妹穿的比我還像新娘却桶。我一直安慰自己,他們只是感情好蔗牡,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布颖系。 她就那樣靜靜地躺著,像睡著了一般辩越。 火紅的嫁衣襯著肌膚如雪嘁扼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天黔攒,我揣著相機與錄音趁啸,去河邊找鬼。 笑死督惰,一個胖子當著我的面吹牛不傅,可吹牛的內容都是我干的。 我是一名探鬼主播赏胚,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼访娶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了觉阅?” 一聲冷哼從身側響起崖疤,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤秘车,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后戳晌,有當地人在樹林里發(fā)現了一具尸體鲫尊,經...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡痴柔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年沦偎,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咳蔚。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡豪嚎,死狀恐怖,靈堂內的尸體忽然破棺而出谈火,到底是詐尸還是另有隱情侈询,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布糯耍,位于F島的核電站扔字,受9級特大地震影響,放射性物質發(fā)生泄漏温技。R本人自食惡果不足惜革为,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舵鳞。 院中可真熱鬧震檩,春花似錦、人聲如沸蜓堕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽套才。三九已至迂猴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間背伴,已是汗流浹背沸毁。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挂据,地道東北人以清。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像崎逃,于是被迫代替她去往敵國和親掷倔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348