備忘錄模式

備忘錄模式

案例

張三一天在下班回家后,想放松一下,于是打開了電腦繼續(xù)昨天沒有挑戰(zhàn)成功的 Boos 游戲關(guān)卡。這個(gè)游戲關(guān)卡相當(dāng)?shù)睦щy,張三已經(jīng)打了很多天了還是沒有挑戰(zhàn)成功,但是還好張三可以保存他在挑戰(zhàn) Boss 之前的狀態(tài),不然的話他剛剛得到的幸運(yùn)道具提升的角色狀態(tài)就會(huì)被 Boss 打掉了。下面用代碼的形式來模擬這一過程:

1.首先是玩家類:

/**
 * 游戲玩家角色類
 */
public class Player {
    // 玩家姓名
    private String name;
    // 生命值
    private int vitality;
    // 法力值
    private int mana;
    // 攻擊值
    private int attack;
    // 防御值
    private int defense;

    public Player(String name) {
        this.name = name;
        // 創(chuàng)建玩家時(shí)初始化各個(gè)狀態(tài)值
        this.vitality = 50;
        this.mana = 50;
        this.attack = 50;
        this.defense = 50;
        show();
    }

    public void show() {
        System.out.println("玩家{" +
                "姓名='" + name + '\'' +
                ", 生命值=" + vitality +
                ", 法力值=" + mana +
                ", 攻擊值=" + attack +
                ", 防御值=" + defense +
                '}');
    }

    // 省略 getter/setter
}

2.模擬游戲過程:

public class Main {
    public static void main(String[] args) {
        Player player = new Player("張三");
        System.out.println("玩家[" + player.getName() + "]獲得幸運(yùn)道具獎(jiǎng)勵(lì),狀態(tài)提升↑");
        player.setVitality(100);
        player.setMana(100);
        player.setAttack(100);
        player.setDefense(100);
        System.out.println("提升后各狀態(tài)值:");
        player.show();
        System.out.println("玩家將與 Boss PK,先保存一下獲得幸運(yùn)道具時(shí)的狀態(tài)");
        Player backup = new Player(player.getName());
        backup.setVitality(player.getVitality());
        backup.setMana(player.getMana());
        backup.setAttack(player.getAttack());
        backup.setDefense(player.getDefense());
        System.out.println("玩家[" + player.getName() + "]與 Boss PK,狀態(tài)下降↓");
        player.setVitality(0);
        player.setMana(0);
        player.setAttack(10);
        player.setDefense(10);
        System.out.println("下降后各狀態(tài)值:");
        player.show();
        System.out.println("玩家恢復(fù)到獲得幸運(yùn)道具時(shí)的狀態(tài)");
        player.setVitality(backup.getVitality());
        player.setMana(backup.getMana());
        player.setAttack(backup.getAttack());
        player.setDefense(backup.getDefense());
        System.out.println("恢復(fù)后各狀態(tài):");
        player.show();
    }
}

3.模擬結(jié)果

玩家{姓名='張三', 生命值=50, 法力值=50, 攻擊值=50, 防御值=50}
玩家[張三]獲得幸運(yùn)道具獎(jiǎng)勵(lì),狀態(tài)提升↑
提升后各狀態(tài)值:
玩家{姓名='張三', 生命值=100, 法力值=100, 攻擊值=100, 防御值=100}
玩家將與 Boss PK,先保存一下獲得幸運(yùn)道具時(shí)的狀態(tài)
玩家{姓名='張三', 生命值=50, 法力值=50, 攻擊值=50, 防御值=50}
玩家[張三]與 Boss PK,狀態(tài)下降↓
下降后各狀態(tài)值:
玩家{姓名='張三', 生命值=0, 法力值=0, 攻擊值=10, 防御值=10}
玩家恢復(fù)到獲得幸運(yùn)道具時(shí)的狀態(tài)
恢復(fù)后各狀態(tài):
玩家{姓名='張三', 生命值=100, 法力值=100, 攻擊值=100, 防御值=100}

可以看到,因?yàn)閺埲诖?Boss 之前保存了之前的狀態(tài),在挑戰(zhàn)失敗后也可以恢復(fù)到之前的狀態(tài)。這種方式與設(shè)計(jì)模式中的備忘錄模式的思想非常相似,下面就來介紹一下備忘錄模式。

模式介紹

備忘錄模式(Memento Pattern)又叫做快照模式(Snapshot Pattern)或Token模式,是GoF的23種設(shè)計(jì)模式之一,屬于行為模式。

定義:在不破壞封閉的前提下,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài)。這樣以后就可將該對(duì)象恢復(fù)到原先保存的狀態(tài)。

角色構(gòu)成

  • Originator(原發(fā)器):它是一個(gè)普通類,可以創(chuàng)建一個(gè)備忘錄,并存儲(chǔ)它的當(dāng)前內(nèi)部狀態(tài),也可以使用備忘錄來恢復(fù)其內(nèi)部狀態(tài),一般將需要保存內(nèi)部狀態(tài)的類設(shè)計(jì)為原發(fā)器。
  • Memento(備忘錄):存儲(chǔ)原發(fā)器的內(nèi)部狀態(tài),根據(jù)原發(fā)器來決定保存哪些內(nèi)部狀態(tài)。備忘錄的設(shè)計(jì)一般可以參考原發(fā)器的設(shè)計(jì),根據(jù)實(shí)際需要確定備忘錄類中的屬性。需要注意的是,除了原發(fā)器本身與負(fù)責(zé)人類之外,備忘錄對(duì)象不能直接供其他類使用,原發(fā)器的設(shè)計(jì)在不同的編程語言中實(shí)現(xiàn)機(jī)制會(huì)有所不同。
  • Caretaker(負(fù)責(zé)人):負(fù)責(zé)人又稱為管理者,它負(fù)責(zé)保存?zhèn)渫?,但是不能?duì)備忘錄的內(nèi)容進(jìn)行操作或檢查。在負(fù)責(zé)人類中可以存儲(chǔ)一個(gè)或多個(gè)備忘錄對(duì)象,它只負(fù)責(zé)存儲(chǔ)對(duì)象,而不能修改對(duì)象,也無須知道對(duì)象的實(shí)現(xiàn)細(xì)節(jié)。

UML類圖

備忘錄模式 UML 類圖

在使用備忘錄模式時(shí),首先應(yīng)該存在一個(gè)原發(fā)器類Originator,在真實(shí)業(yè)務(wù)中,原發(fā)器類是一個(gè)具體的業(yè)務(wù)類,它包含一些用于存儲(chǔ)成員數(shù)據(jù)的屬性。對(duì)于備忘錄類Memento而言,它通常提供了與原發(fā)器相對(duì)應(yīng)的屬性(可以是全部,也可以是部分)用于存儲(chǔ)原發(fā)器的狀態(tài)。對(duì)于負(fù)責(zé)人類Caretaker,它用于保存?zhèn)渫泴?duì)象,并提供getMemento()方法用于向客戶端返回一個(gè)備忘錄對(duì)象,原發(fā)器通過使用這個(gè)備忘錄對(duì)象可以回到某個(gè)歷史狀態(tài)。

代碼改造

下面就通過代碼來對(duì)上面的案例進(jìn)行改造:

1.原發(fā)器類,很明顯就是這里的玩家角色類:

/**
 * 原發(fā)器類
 */
public class Player {
    // 玩家姓名
    private String name;
    // 生命值
    private int vitality;
    // 法力值
    private int mana;
    // 攻擊值
    private int attack;
    // 防御值
    private int defense;

    public Player(String name) {
        this.name = name;
        // 創(chuàng)建玩家時(shí)初始化各個(gè)狀態(tài)默認(rèn)值
        this.vitality = 50;
        this.mana = 50;
        this.attack = 50;
        this.defense = 50;
        show();
    }

    // 創(chuàng)建一個(gè)備忘錄對(duì)象
    public Memento createMemento() {
        return new Memento(this);
    }

    // 根據(jù)備忘錄對(duì)象恢復(fù)原發(fā)器狀態(tài)
    public void restoreMemento(Memento memento) {
        this.name = memento.getName();
        this.vitality = memento.getVitality();
        this.mana = memento.getMana();
        this.attack = memento.getAttack();
        this.defense = memento.getDefense();
    }

    public void show() {
        System.out.println("玩家{" +
                "姓名='" + name + '\'' +
                ", 生命值=" + vitality +
                ", 法力值=" + mana +
                ", 攻擊值=" + attack +
                ", 防御值=" + defense +
                '}');
    }

    // 省略 getter/setter...
}

2.備忘錄類:

/**
 * 備忘錄角色類
 */
public class Memento {
    // 玩家姓名
    private String name;
    // 生命值
    private int vitality;
    // 法力值
    private int mana;
    // 攻擊值
    private int attack;
    // 防御值
    private int defense;

    public Memento(Player player) {
        this.name = player.getName();
        this.vitality = player.getVitality();
        this.mana = player.getMana();
        this.attack = player.getAttack();
        this.defense = player.getDefense();
    }

    // 省略 getter/setter...
}

3.負(fù)責(zé)人類:

/**
 * 負(fù)責(zé)人類
 */
public class Caretaker {
    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

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

4.模擬過程:

public class Main {
    public static void main(String[] args) {
        Caretaker caretaker = new Caretaker();
        Player player = new Player("張三");
        System.out.println("玩家[" + player.getName() + "]獲得幸運(yùn)道具獎(jiǎng)勵(lì),狀態(tài)提升↑");
        player.setVitality(100);
        player.setMana(100);
        player.setAttack(100);
        player.setDefense(100);
        System.out.println("提升后各狀態(tài)值:");
        player.show();
        System.out.println("玩家將與 Boss PK,先保存一下獲得幸運(yùn)道具時(shí)的狀態(tài)");
        caretaker.setMemento(player.createMemento());
        System.out.println("玩家[" + player.getName() + "]與 Boss PK,狀態(tài)下降↓");
        player.setVitality(0);
        player.setMana(0);
        player.setAttack(10);
        player.setDefense(10);
        System.out.println("下降后各狀態(tài)值:");
        player.show();
        System.out.println("玩家恢復(fù)到獲得幸運(yùn)道具時(shí)的狀態(tài)");
        player.restoreMemento(caretaker.getMemento());
        System.out.println("恢復(fù)后各狀態(tài):");
        player.show();
    }
}

5.模擬結(jié)果:

玩家{姓名='張三', 生命值=50, 法力值=50, 攻擊值=50, 防御值=50}
玩家[張三]獲得幸運(yùn)道具獎(jiǎng)勵(lì),狀態(tài)提升↑
提升后各狀態(tài)值:
玩家{姓名='張三', 生命值=100, 法力值=100, 攻擊值=100, 防御值=100}
玩家將與 Boss PK,先保存一下獲得幸運(yùn)道具時(shí)的狀態(tài)
玩家[張三]與 Boss PK,狀態(tài)下降↓
下降后各狀態(tài)值:
玩家{姓名='張三', 生命值=0, 法力值=0, 攻擊值=10, 防御值=10}
玩家恢復(fù)到獲得幸運(yùn)道具時(shí)的狀態(tài)
恢復(fù)后各狀態(tài):
玩家{姓名='張三', 生命值=100, 法力值=100, 攻擊值=100, 防御值=100}

可以看到效果是一樣的,但是使用了備忘錄模式之后,他將恢復(fù)前的狀態(tài)用備忘錄類Memento類來進(jìn)行保存,在恢復(fù)時(shí)用負(fù)責(zé)人類Caretaker類進(jìn)行保存?zhèn)渫?,使得類之間的職責(zé)更加清晰。

模式應(yīng)用

備忘錄模式在很多軟件的使用過程中普遍存在,但是在應(yīng)用軟件開發(fā)中,它的使用頻率并不太高,因?yàn)楝F(xiàn)在很多基于窗體和瀏覽器的應(yīng)用軟件并沒有提供撤銷操作。如果需要為軟件提供撤銷功能,備忘錄模式無疑是一種很好的解決方案。在一些字處理軟件、圖像編輯軟件、數(shù)據(jù)庫管理系統(tǒng)等軟件中備忘錄模式都得到了很好的應(yīng)用。下面列舉一些備忘錄思想的場(chǎng)景:瀏覽器回退、數(shù)據(jù)庫備份與還原、編輯器撤銷與重做、棋牌游戲悔棋等。

總結(jié)

1.主要優(yōu)點(diǎn)

  • 它提供了一種狀態(tài)恢復(fù)的實(shí)現(xiàn)機(jī)制,使得用戶可以方便地回到一個(gè)特定的歷史步驟,當(dāng)新的狀態(tài)無效或者存在問題時(shí),可以使用暫時(shí)存儲(chǔ)起來的備忘錄將狀態(tài)復(fù)原。
  • 備忘錄實(shí)現(xiàn)了對(duì)信息的封裝,一個(gè)備忘錄對(duì)象是一種原發(fā)器對(duì)象狀態(tài)的表示,不會(huì)被其他代碼所改動(dòng)。備忘錄保存了原發(fā)器的狀態(tài),采用列表、堆棧等集合來存儲(chǔ)備忘錄對(duì)象可以實(shí)現(xiàn)多次撤銷操作。

2.主要缺點(diǎn)

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

3.適用場(chǎng)景

  • 保存一個(gè)對(duì)象在某一個(gè)時(shí)刻的全部狀態(tài)或部分狀態(tài),這樣以后需要時(shí)它能夠恢復(fù)到先前的狀態(tài),實(shí)現(xiàn)撤銷操作。
  • 防止外界對(duì)象破壞一個(gè)對(duì)象歷史狀態(tài)的封裝性,避免將對(duì)象歷史狀態(tài)的實(shí)現(xiàn)細(xì)節(jié)暴露給外界對(duì)象。

參考資料

本篇文章github代碼地址:https://github.com/Phoegel/design-pattern/tree/main/memento
轉(zhuǎn)載請(qǐng)說明出處,本篇博客地址:http://www.itdecent.cn/p/fda7a3c871b1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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