备忘录模式浅析

在平时,我们可能需要在某些时候对状态进行保存,然后可以恢复到之前的状态。比如:下棋的时候可能会出现悔棋,恢复到上一步或者更上一步的状态。再比如,文本编辑器,会有撤销的场景。再比如,玩通关游戏,关卡可能分很多小关,打一个小Boss后需要打一个大Boss,我们需要在某些阶段对游戏快照进行保存,这样可能恢复到之前的状态,继续玩耍。

这种将对象状态外部化存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态,这就是本文今天要说的备忘录模式,又叫快照模式或者Token模式。

一. 备忘录模式的基本介绍

意图

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

结构

备忘录模式的基本结构如下:

这里涉及到的参与者有如下几种:

  • Memento(备忘录)
    • 备忘录存储原发器对象的内部状态。原发器根据需要决备忘录存储原发器的哪些内部状态。
  • Originator(原发器)
    • 原发器创建一个备忘录,用于记录当前时刻它的内部状态
    • 使用备忘录恢复内部状态。
  • Caretaker(负责人)
    • 负责保存好备忘录
    • 不能对备忘录的内容进行操作或者检查。

二. 备忘录模式的示例

接下来,我们就模拟一个游戏存档的场景来给出一个备忘录模式的示例。

  • Game.java(原发器)
package com.wangmengjun.tutorial.designpattern.memento;

public class Game {

  private String description;

    private int score;

    private int nums;

    public GameProgressMemento createMemento() {
      return new GameProgressMemento(description, score, nums);
    }

    public void undoFromGameProgressMemento(GameProgressMemento mementor) {
      this.score = mementor.getScore();
      this.nums = mementor.getNums();
      this.description = mementor.getDescription();
    }


  /**
   * @return the description
   */
  public String getDescription() {
    return description;
  }

  /**
   * @param description the description to set
   */
  public void setDescription(String description) {
    this.description = description;
  }

  /**
   * @return the score
   */
  public int getScore() {
    return score;
  }

  /**
   * @param score the score to set
   */
  public void setScore(int score) {
    this.score = score;
  }

  /**
   * @return the nums
   */
  public int getNums() {
    return nums;
  }

  /**
   * @param nums the nums to set
   */
  public void setNums(int nums) {
    this.nums = nums;
  }

  @Override
  public String toString() {
    return "Game [description(游戏简单描述)=" + description + ", score(得分)=" + score + ", nums(剩余生命数)=" + nums + "]";
  }


}
  • GameProgressMemento.java(备忘录)
package com.wangmengjun.tutorial.designpattern.memento;

import java.io.Serializable;

public class GameProgressMemento implements Serializable {

  private static final long serialVersionUID = -6283136884189402182L;

  private String description;

    private int score;

    private int nums;

  public GameProgressMemento(String description, int score, int nums) {
    super();
    this.description = description;
    this.score = score;
    this.nums = nums;
  }

  /**
   * @return the description
   */
  public String getDescription() {
    return description;
  }

  /**
   * @param description the description to set
   */
  public void setDescription(String description) {
    this.description = description;
  }

  /**
   * @return the score
   */
  public int getScore() {
    return score;
  }

  /**
   * @param score the score to set
   */
  public void setScore(int score) {
    this.score = score;
  }

  /**
   * @return the nums
   */
  public int getNums() {
    return nums;
  }

  /**
   * @param nums the nums to set
   */
  public void setNums(int nums) {
    this.nums = nums;
  }

  @Override
  public String toString() {
    return "GameProgressMemento [description=" + description + ", score=" + score + ", nums=" + nums + "]";
  }

}
  • GameProgressCaretaker.java(负责人)
package com.wangmengjun.tutorial.designpattern.memento;

import java.util.Stack;

public class GameProgressCaretaker {
  
  private Stack<GameProgressMemento> mememtors = new Stack<>();

  public void add(GameProgressMemento memento) {
    mememtors.add(memento);
  }

  public GameProgressMemento getMemento() {
    return this.mememtors.pop();
  }

}
  • Client.java(客户端)
package com.wangmengjun.tutorial.designpattern.memento;

public class Client {

  public static void main(String[] args) {

    /**
     * 1、游戏开始描述
     */
    Game game = new Game();
    game.setNums(3);
    game.setScore(0);
    game.setDescription("游戏开始");
    System.out.println(game);

    System.out.println();
    /**
     * 2、打完小Boss,保存一个备忘记录
     */
    game.setNums(2);
    game.setScore(15000);
    game.setDescription("打掉小Boss");
    System.out.println(game);

    System.out.println("*******************************创建一个游戏进度备忘********************************************************");
    GameProgressMemento memento = game.createMemento();
    GameProgressCaretaker caretaker = new GameProgressCaretaker();
    caretaker.add(memento);

    /**
     * 3、打不过大Boss
     */
    System.out.println();
    System.out.println("打大Boss剩余1条命,保存一下");
    game.setNums(1);
    game.setScore(36000);
    game.setDescription("打大Boss剩余1条命");
    System.out.println(game);
    System.out.println("*******************************创建一个游戏进度备忘********************************************************");
    caretaker.add(game.createMemento());

    /**
     * 4、打大Boss失败
     */
    game.setNums(0);
    game.setScore(51200);
    game.setDescription("打大Boss失败,剩余0条命");
    System.out.println();
    System.out.println("打大Boss失败,游戏信息");
    System.out.println(game);
    System.out.println();
    System.out.println("打大Boss发现打不过");
    game.setNums(0);
    game.setScore(36000);
    System.out.println("打大Boss失败,游戏信息");
    System.out.println(game);

    /**
     * 5、恢复到之前的打完小Boss的状态
     */
    System.out.println();
    System.out.println("*******************************恢复到上一个游戏状态********************************************************");
    game.undoFromGameProgressMemento(caretaker.getMemento());
    System.out.println("恢复后的游戏信息");
    System.out.println(game);

    System.out.println();

    System.out.println("*******************************恢复到上一个游戏状态********************************************************");
    game.undoFromGameProgressMemento(caretaker.getMemento());
    System.out.println("恢复后的游戏信息");
    System.out.println(game);

    System.out.println();


  }
}

输出结果:

Game [description(游戏简单描述)=游戏开始, score(得分)=0, nums(剩余生命数)=3]

Game [description(游戏简单描述)=打掉小Boss, score(得分)=15000, nums(剩余生命数)=2]
*******************************创建一个游戏进度备忘********************************************************

打大Boss剩余1条命,保存一下
Game [description(游戏简单描述)=打大Boss剩余1条命, score(得分)=36000, nums(剩余生命数)=1]
*******************************创建一个游戏进度备忘********************************************************

打大Boss失败,游戏信息
Game [description(游戏简单描述)=打大Boss失败,剩余0条命, score(得分)=51200, nums(剩余生命数)=0]

打大Boss发现打不过
打大Boss失败,游戏信息
Game [description(游戏简单描述)=打大Boss失败,剩余0条命, score(得分)=36000, nums(剩余生命数)=0]

*******************************恢复到上一个游戏状态********************************************************
恢复后的游戏信息
Game [description(游戏简单描述)=打大Boss剩余1条命, score(得分)=36000, nums(剩余生命数)=1]

*******************************恢复到上一个游戏状态********************************************************
恢复后的游戏信息
Game [description(游戏简单描述)=打掉小Boss, score(得分)=15000, nums(剩余生命数)=2]

至此一个简单的游戏状态保存和状态恢复的示例就完成了。

三. 小结

优缺点

优点:

1、有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取,这时,使用备忘录模式可以把复杂的发起人内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界。

2、本模式简化了发起人类。发起人不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理他们所需要的这些状态的版本。

3、当发起人角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。

缺点:

1、如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。

2、当负责人角色将一个备忘录 存储起来的时候,负责人可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否很昂贵。

3、当发起人角色的状态改变的时候,有可能这个协议无效。如果状态改变的成功率不高的话,不如采取“假如”协议模式。

参考

[1]. 阎宏. Java与模式.电子工业出版社

[2]. Erich Gamma. 设计模式-可复用面向对象软件的基础. 机械工业出版社.

本文分享自微信公众号 - 孟君的编程札记(gh_0f0f5e0ae1de),作者:孟君

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JPinYin,一个汉字拼音转换的利器,你值得拥有

    在某些场景中,可能为了方便用户快速搜索,使用拼音首字母的方式进行检索。举个例子,一个系统支持拼音首字母检索,那么输入hzlj就可以搜索出杭州龙井等商品结果,系统...

    孟君
  • HashSet的实现原理浅析

    HashSet类继承AbstractSet,实现Set接口、实现了Cloneable接口以及序列化Serializable接口~如:

    孟君
  • 给定整数数组,输出所有和为S的可能组合

    如果给你一个题目,“给定一个整数数组和一个目标数S,如何输出该数组中所有和为S的可能组合?”,你会如何做呢?

    孟君
  • PizzaGAN:以前你教我做披萨饼,现在让我来教你

    你可能不会做披萨饼,但现在你的深度学习模型已经学会了。麻省理工学院最新发布的深度学习模型PizzaGAN通过基于组合层的GAN模型来学习如何训练GAN模型以识别...

    深度学习与Python
  • 厉害了!新闻情绪因子

    2、智能标签识别:识别新闻中存在的法人及自然人实体、SAM产品、行业、事件及概念。除了识别出这些标签,算法还能给出这篇新闻与这些标签的相关程度。

    量化投资与机器学习微信公众号
  • 使用rclone迁移FTP数据到COS

    与ftp配置一样,同样可以通过rclone config来配置cos,但我们也可以通过直接修改配置文件的方式来配置cos:

    ictfox
  • 【陆勤践行】DataSchool 推荐的数据科学资源

    Blogs Simply Statistics1: Written by the Biostatistics professors at Johns Hopki...

    陆勤_数据人网
  • 线程上下文类加载器ContextClassLoader内存泄漏隐患

    今天(2020-01-18)在编写Netty相关代码的时候,从Netty源码中的ThreadDeathWatcher和GlobalEventExecutor追溯...

    Throwable
  • 哈佛开发目前最小最快爬虫机器人:能跑能跳,只有蟑螂小腿高

    在ICRA2020国际大会上,来自哈佛大学和科罗拉多大学博尔德分校的Kaushik Jayaram、Jennifer Shum、Samantha Castell...

    大数据文摘
  • 经验之谈

    刚开始接触python是在我大三上学期的时候,有一点C语言的基础。语言的基础其实在各类编程语言都是互通的,只是被包装的不一样,只要掌握一门语言的基础,学习其他语...

    佛系编程人

扫码关注云+社区

领取腾讯云代金券