Java設計模式之——備忘錄模式

備忘錄模式簡單介紹

備忘錄模式是一種行爲模式,該模式用於保存對象當前的狀態,並且在之後可以再次恢復到此狀態,這有點像我們平時說的“後悔藥”。備忘錄模式實現的方式需要保證被保存的對象狀態不嗯呢該被對象從外部訪問,目的是爲了保護號被保存的這些對象狀態的完整性以及內部實現不向外暴露。

備忘錄模式的定義

在不破壞封閉的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,這樣,以後就可將該對象恢復到原先保存的狀態。

備忘錄模式的使用場景

  • (1)需要保存一個對象在某一個時刻的狀態或部分狀態。
  • (2)如果用一個接口來讓其他對象得到這些狀態,將會暴露對象的實現細節並破壞對象的封裝性,一個對象不希望外界直接訪問其內部狀態,通過中間對象可以間接訪問其內部狀態。

說白了它主要應用在:對象在執行某些操作爲防止意外而在執行操作前將對象狀態備份的場景,有點類似於事務回滾的意思。

備忘錄模式的 UML 類圖

這裏寫圖片描述

角色介紹:

  • Originator:發起人角色,負責創建一個備忘錄,可以記錄、恢復自身的內部狀態。同時 Originator 還可以根據需要決定 Memento 存儲自身的哪些內部狀態。
  • Memento:備忘錄角色,用於存儲 Originator 的內部狀態,並且可以防止 Originator 以外的對象訪問 Memento(Originator 的內部狀態)。
  • Caretaker:負責任角色,負責存儲備忘錄,不能對備忘錄的內容進行操作和訪問,只能夠將備忘錄傳遞給其他對象。

備忘錄模式的簡單實戰

對於備忘錄模式來說,比較貼切的場景應該是遊戲中的存檔功能,該功能就是將遊戲進度存儲到本地文件系統或者數據庫中,下次再次進入時從本地加載進度,使得玩家能夠繼續上一次的遊戲之旅。這裏我們以遊戲存檔爲例來簡單演示一下備忘錄模式的實現。

首先我們建立遊戲類、備忘錄類、Caretaker(管理)類,玩遊戲到某個節點對遊戲進行存檔,然後退出遊戲,再次進入遊戲時從存檔中讀取進度,並且存檔時的進度:

先看遊戲類:

public class Game {
    private String name;    //玩家姓名
    private int position;   //當前關卡

    //開始遊戲
    public void play() {
        Log.e("Game", "當前玩家:" + this.name + "\r\n"
                + "當前存檔:" + (position == 0 ? "1 個" : "無") + "\r\n"
                + "當前遊戲進度:" + "第 " + position + " 關");
        position++;
    }

    //退出遊戲
    public void quit() {
        Log.e("Game", "當前玩家:" + this.name + "\r\n"
                + "當前遊戲進度:" + "第 " + position + " 關");
    }

    //創建備忘錄
    public Memento createMemento() {
        Memento memento = new Memento();
        memento.setName(name);
        memento.setPosition(position);
        return memento;
    }

    //恢復遊戲
    public void restore(Memento memento){
        this.name = memento.getName();
        this.position = memento.getPosition();
    }
}

在 Game 遊戲類中國,我們存儲了幾個關鍵字段(也就是所謂的狀態),玩家的姓名以及當前通過的關卡。當調用 play 函數時表示開始進行遊戲,沒調用一次表示通過一個關卡。在該類中可以通過 createMemento 函數來創建當前用戶的遊戲進度(備忘錄對象),也就是將自身的狀態保存到一個 Memento 對象中。外部可以通過 restore 函數將 Game 對象的狀態從備忘錄對象中恢復。

我們再來看看備忘錄對象,它只是存儲 Game 對象的字段(狀態信息),具體代碼如下:

public class Memento {
    private String name;    //玩家姓名
    private int position;   //當前關卡

    public String getName() {
        return name;
    }

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

    public int getPosition() {
        return position;
    }

    public void setPosition(int position) {
        this.position = position;
    }
}

這個類只用來負責存儲 Originator(發起人)角色的一些數據,防止外部直接訪問 Originator 內部的狀態。

而負責人的角色則爲 Caretaker 角色,我們來看下它都做了什麼操作:

public class Caretaker {
    private Memento memento;    //當前遊戲狀態

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

    //獲取存檔
    public Memento restoreMemento() {
        return memento;
    }
}

Caretaker 類的職責很簡單,就是負責管理 Memento 對象,也就是備忘錄對象。
我們再來看一下客戶端代碼的實現:

        //構建遊戲對象
        Game game = new Game();
        //打遊戲
        game.play();    //通過關卡 1
        game.play();    //通過關卡 2
        game.play();    //通過關卡 3
        game.play();    //通過關卡 4
        game.play();    //通過關卡 5

        //不玩了 存檔
        Caretaker caretaker = new Caretaker();
        caretaker.storeMemento(game.createMemento());

        //突然又想玩了繼續玩...
        game.play();    //通過關卡 6
        game.play();    //通過關卡 7
        game.play();    //通過關卡 8

        //突然斷電,恢復遊戲(從遊戲存檔中繼續遊戲進度)
        game.restore(caretaker.restoreMemento());   //恢復到關卡 5

Game 在這裏爲 Originator 角色,也就是需要存儲數據的對象,在這裏並沒有直接存儲 Game 對象,而是通過 Memento 對 Game 對象的數據進行存儲,然後再存儲 Memento 對象,最終對 Memento 的存儲操作則交給 Caretaker 對象。在這個過程中,各個角色職責清晰、單一,代碼也比較簡單,既對外屏蔽了對 Game 角色狀態的直接訪問,在滿足了對象狀態存取功能的同時也使得該模塊的結構保持清晰、整潔。

總結

備忘錄模式是在不破壞封裝的條件下,通過備忘錄對象(Memento)存儲另外一個對象內部狀態的快照,在將來合適的時候把這個對象還原到存儲起來的狀態。

優點

  • (1)給用戶提供了一種可以恢復狀態的機制,可以使用戶能夠比較方便地回到某個歷史的狀態。
  • (2)實現了信息的封裝,使得用戶不需要關係狀態的保存細節。

缺點

  • 消耗資源,如果類的成員變量過多,勢必會佔用比較大的資源,而且每一次保存都會消耗一定的內存(因爲需要重新創建 Memento 對象進行保存 Originator 中的各種狀態)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章