行为模式之备忘录模式

Posted by Night Field's Blog on June 4, 2020

1 概述

备忘录模式(Memento Pattern),又叫Token模式,它提供了一种方式,来捕捉对象某一时刻的内部状态,并将其保存成备忘录(Memento),如此一来,对象可以根据此备忘录恢复到之前的状态。

2 备忘录模式

几乎所有的编辑器都支持撤销功能,这其实就是备忘录模式的例子,撤销操作,使得文本得以恢复到之前的状态。面向对象设计中,备忘录模式的实现一般需要三个角色:

  1. 发起人(Originator):主对象,提供将内容保存成备忘录,或者从备忘录恢复状态的功能。
  2. 备忘录(Memento):备忘录对象,保存了主对象的一些历史状态。同时,为了防止内容被破坏和修改,它可以实现一些访问控制逻辑。
  3. 负责人(Caretaker):负责保存备忘录对象。

3 案例

想必很多游戏玩家对Save-Load大法都不陌生。在打boss之前,我们可以保存一下游戏状态,如果没有打赢,我们可以从之前保存的存档重来————典型的备忘录模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 发起人对象
public class Game {
    int gameStage = 1;
    int characterLevel = 1;
    int monsterKilled = 0;
    public void play() throws InterruptedException {
        System.out.println("Playing Game...");
        gameStage += 1;
        characterLevel += 5;
        monsterKilled += 10;
    }
    // 创建一个存档(备忘录)
    public GameArchive save() {
        System.out.println("game saved!");
        return new GameArchive(gameStage, characterLevel, monsterKilled);
    };
    // 从存档恢复状态
    public void restore(GameArchive archive) {
        System.out.println("game rollback!");
        gameStage = archive.getGameStage();
        characterLevel = archive.getCharacterLevel();
        monsterKilled = archive.getMonsterKilled();
    }
    @Override
    public String toString() {
        return "game stage is " + gameStage +
                ", character level is " + characterLevel +
                ", monster killed is " + monsterKilled;
    }
}
// 备忘录对象,保存游戏状态
public class GameArchive {
    int gameStage = 1;
    int characterLevel = 1;
    int monsterKilled = 0;
    public GameArchive(int gameStage, int characterLevel, int monsterKilled) {
        this.gameStage = gameStage;
        this.characterLevel = characterLevel;
        this.monsterKilled = monsterKilled;
    }
    public int getGameStage() {
        return gameStage;
    }
    public int getCharacterLevel() {
        return characterLevel;
    }
    public int getMonsterKilled() {
        return monsterKilled;
    }
}
// 负责人对象,持有备忘录
public class ArchiveManager {
    // 如果又多个存档,将是个List<GameArchive>
    GameArchive archive;
    public GameArchive getArchive() {
        return archive;
    }
    public void setArchive(GameArchive archive) {
        this.archive = archive;
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        ArchiveManager manager = new ArchiveManager();
        Game dmc = new Game();
        dmc.play();
        System.out.println("Game init status: " + dmc);
        manager.setArchive(dmc.save());
        dmc.play();
        System.out.println("Game new status: " + dmc);
        dmc.restore(manager.getArchive());
        System.out.println("Game status after restore: " + dmc);
    }
}

输出:

1
2
3
4
5
6
7
Playing Game...
Game init status: game stage is 2, character level is 6, monster killed is 10
game saved!
Playing Game...
Game new status: game stage is 3, character level is 11, monster killed is 20
game rollback!
Game status after restore: game stage is 2, character level is 6, monster killed is 10

将游戏状态保存为GameArchive对象,后续可以方便地将游戏进度回退。备忘录模式可以说是所有游戏的标配,它实现了信息的封装,使得用户不需要关心状态的保存细节。一般情况下,备忘录对象可以有多个,以供主对象选择恢复到何时的状态。但备忘录不宜过多,否则可能会占用过多的内存。 理论上来说,备忘录对象都可以通过序列化(Serializable)来实现。

4 总结

备忘录模式可以在不破坏封装的前提下,捕获一个类的内部状态,并且在该对象之外保存该状态,保证该对象能够恢复到历史的某个状态。

文中例子的github地址