:::info 备忘录模式听起来特别高深,其实可能写过几年代码的都不知不觉的用了很多次了。模式的名称其实已经很形象的反映出其作用了:就是为了在某一时刻把当前的状态记录下来,以后再恢复到那时的状态。 :::

定义

在不破坏封闭的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态,从而可以将对象恢复到原先保存的状态

使用场景

当你正在开发一个功能,这个功能需要存档的时候就应该想到它。例如游戏,文档编辑器等等,都需要在你下次重新打开的时候恢复到你关闭它时候的状态。

  • 后悔药。
  • 打游戏时的存档。
  • Windows 里的 ctri + z。
  • IE 中的后退。
  • 数据库的事务管理。

    UML

    备忘录模式 - 图1

    角色结构

  • Originator:发起人角色

  • Memento :备忘录角色
  • CareTaker:备忘录管理员角色

    优点

  • 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。

  • 实现了信息的封装,使得用户不需要关心状态的保存细节。

    缺点

  • 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

    业务场景

    打游戏的时候存档。

    代码示例

    GameOriginator 定义游戏类

    1. public class GameOriginator {
    2. private int currentScore;
    3. /**
    4. * 将需要保存的状态分装在Memento里对外提供
    5. */
    6. public GameProgressMemento saveProcess() {
    7. return new GameProgressMemento(currentScore);
    8. }
    9. /**
    10. * 通过从外部接收的Memento恢复状态
    11. */
    12. public void restoreProcess(GameProgressMemento memento) {
    13. currentScore = memento.getScore();
    14. }
    15. /**
    16. * 对内部状态的使用
    17. */
    18. public void playGame() {
    19. System.out.println("------------------开始游戏------------------");
    20. System.out.println("当前分数为:"+ currentScore);
    21. System.out.println("杀死一个小怪物得1分");
    22. currentScore++;
    23. System.out.println(String.format("总分为:%d", currentScore));
    24. }
    25. public void exitGame(){
    26. System.out.println("退出游戏");
    27. currentScore=0;
    28. System.out.println("-----------------退出游戏-------------------");
    29. }
    30. }

    GameProgressMemento 构建备忘录

    这个类最简单,其基本上就是一个POJO。它不包含业务逻辑,只包含状态数据,结构由要保存的状态类决定。例如我们这里只保存一个内部状态,游戏分数。

    1. public class GameProgressMemento {
    2. private int score;
    3. public GameProgressMemento(int score) {
    4. this.score = score;
    5. }
    6. public int getScore() {
    7. return score;
    8. }
    9. }

    GameCareTaker 构建游戏缓存

    如果说备忘录模式有一点点技巧的话,也就是这个类了。CareTaker相对于Originator来说是一个外部组件,它帮助Originator保存了状态,相当于Originator将自己某一个时刻的状态保存到了外部。

当我们要保存状态时,使用此类的saveMemento。当我们要恢复状态时,使用此类的getMemento

  1. public class GameCareTaker {
  2. private List<GameProgressMemento> memento = new ArrayList<>();
  3. public void saveMemento(GameProgressMemento memento) {
  4. System.out.println("游戏存档");
  5. this.memento.add(memento);
  6. }
  7. public GameProgressMemento getMemento(int index) {
  8. return this.memento.get(index);
  9. }
  10. }

Client

  1. public class Client {
  2. public static void main(String[] args) {
  3. GameOriginator originator = new GameOriginator();
  4. GameCareTaker careTaker = new GameCareTaker();
  5. //玩游戏
  6. originator.playGame();
  7. //保存进度
  8. careTaker.saveMemento(originator.saveProcess());
  9. //退出游戏
  10. originator.exitGame();
  11. //重新打开游戏,恢复进度
  12. originator.restoreProcess(careTaker.getMemento(0));
  13. originator.playGame();
  14. }
  15. }

输出

  1. ------------------开始游戏------------------
  2. 当前分数为:0
  3. 杀死一个小怪物得1
  4. 总分为:1
  5. 游戏存档
  6. 退出游戏
  7. -----------------退出游戏-------------------
  8. ------------------开始游戏------------------
  9. 当前分数为:1
  10. 杀死一个小怪物得1
  11. 总分为:2

技术要点总结

  • 首先识别出Originator需要保存的状态,然后构建一个备忘录类Memento。
  • Originator需要提供两个方法,一个用于对外提供包含内部状态的备忘录,一个用于使用外部传递进来的备忘录恢复内部状态。
  • CareTaker 负责保存备忘录,并提供访问方法。