备忘录模式

2023-05-16

备忘录模式

标签 : Java与设计模式


备忘录模式: 在不破坏封装性的前提下, 捕获一个对象的内部状态( or 拷贝), 并在该对象之外保存这个状态, 这样以后就可 将该对象恢复到原先保存的状态.

(图片来源: 设计模式: 可复用面向对象软件的基础)
将保存细节封装在Memento中, 后面即使修改了保存细节也不会影响客户端.


模式实现

案例: 游戏进度保存
在攻击Boss前, 将当前游戏进度保存, 万一失败还可从保存点重新开始.


Originator-原发器

  • 负责创建一个备忘录Memento, 用以记录当前时刻它的内部状态(决定Memento存储哪些内部状态 -不一定是全部属性);
  • 可使用备忘录恢复内部状态.
/**
 * 游戏角色, 原发器
 *
 * @author jifang
 * @since 16/8/29 上午10:05.
 */
public class GameRoleOriginator {

    private int vit;    // 生命值
    private int atk;    // 攻击力
    private int def;    // 防御力

    public GameRoleOriginator(int vit, int atk, int def) {
        this.vit = vit;
        this.atk = atk;
        this.def = def;
    }

    public void fight() {
        vit -= 10;
        atk -= 8;
        def += 10;
    }

    public RoleStateMemento save() {
        return new RoleStateMemento(vit, atk, def);
    }

    public void recover(RoleStateMemento memento) {
        this.setVit(memento.getVit());
        this.setAtk(memento.getAtk());
        this.setDef(memento.getDef());
    }

    public int getVit() {
        return vit;
    }

    public void setVit(int vit) {
        this.vit = vit;
    }

    public int getAtk() {
        return atk;
    }

    public void setAtk(int atk) {
        this.atk = atk;
    }

    public int getDef() {
        return def;
    }

    public void setDef(int def) {
        this.def = def;
    }

    @Override
    public String toString() {
        return "GameRoleOriginator{" +
                "vit=" + vit +
                ", atk=" + atk +
                ", def=" + def +
                '}';
    }
}

Memento-备忘录

  • 负责存储Originator的内部状态(与Originator共同决定存储原发器哪些内部状态);
  • 防止Originator以外对象访问备忘录. Memento实际应该有两个接口: Caretaker只能看到一个窄接口 -只能将备忘录传递给其他对象. Originator能够看到一个宽接口, 允许它访问返回先前状态所需的所有数据(C++中可由friend提供支持). 理想的情况是只允许生成本备忘录的那个原发器访问本备忘录的内部状态.
public class RoleStateMemento {

    private int vit;
    private int atk;
    private int def;

    public RoleStateMemento(int vit, int atk, int def) {
        this.vit = vit;
        this.atk = atk;
        this.def = def;
    }

    public int getVit() {
        return vit;
    }

    public void setVit(int vit) {
        this.vit = vit;
    }

    public int getAtk() {
        return atk;
    }

    public void setAtk(int atk) {
        this.atk = atk;
    }

    public int getDef() {
        return def;
    }

    public void setDef(int def) {
        this.def = def;
    }
}

Caretaker-负责人

  • 负责保存好备忘录Memento;
  • 不能对备忘录内容进行操作或检查.
public class RoleStateCaretaker {

    private Deque<RoleStateMemento> stack = new LinkedList<>();

    public void save(RoleStateMemento memento) {
        stack.push(memento);
    }

    public RoleStateMemento checkout() {
        return stack.pop();
    }
}
  • Client
public class Client {

    @Test
    public void client() {
        RoleStateCaretaker caretaker = new RoleStateCaretaker();

        GameRoleOriginator originator = new GameRoleOriginator(100, 50, 50);
        System.out.println("角色初始状态: " + originator);

        // 保存进度
        caretaker.save(originator.save());

        System.out.println("fight boss...");
        originator.fight();
        System.out.println("阻击Boss后的状态: " + originator);

        originator.recover(caretaker.checkout());
        System.out.println("恢复后的状态: " + originator);
    }
}

序列化所有属性

如果Memento需要保存的是Originator的所有属性, 那么可将Originator的所有属性都存储到一个Map<String, Object>结构中由Caretaker保存, 这样就节省了Memento中间类的开发成本. 甚至还可将Originator序列化为二进制流/字符串存储到持久化设备中(如磁盘、DB、Redis), 节省内存开销, 下面演示将Originator转化为Map<String, Object>存储:

  • Originator
    注意save()/recover()的变化:
public class GameRoleOriginator {

    private int vit;    // 生命值
    private int atk;    // 攻击力
    private int def;    // 防御力

    // ...

    public void fight() {
        vit -= 10;
        atk -= 8;
        def += 10;
    }

    public Map<String, Object> save() {
        try {
            return BeanUtil.bean2Map(this);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }


    public void recover(Map<String, Object> memento) {
        GameRoleOriginator bean;
        try {
            bean = BeanUtil.map2Bean(memento);
        } catch (IllegalAccessException | InstantiationException | NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
        this.setVit(bean.getVit());
        this.setAtk(bean.getAtk());
        this.setDef(bean.getDef());
    }

    // ...
}
  • BeanUtil
public class BeanUtil {

    public static Map<String, Object> bean2Map(Object object) throws IllegalAccessException {
        Map<String, Object> map = new HashMap<>();
        Class<?> clazz = object.getClass();
        map.put("class", clazz);

        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            String key = field.getName();
            Object value = field.get(object);
            map.put(key, value);
        }

        return map;
    }

    public static <T> T map2Bean(Map<String, Object> map) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
        Class<?> clazz = (Class<?>) map.get("class");
        Field[] fields = clazz.getDeclaredFields();
        Object object = clazz.newInstance();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value = map.get(field.getName());
            field.set(object, value);
        }

        return (T) object;
    }
}

小结

  • 适用性

    • 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态;
    • 如果一个用接口来让其他对象直接得到这些状态, 将会暴露对象的实现细节并破坏对象的封装性, 而Memento可以把复杂的对象内部信息对其他的对象屏蔽起来, 从而可以恰当的保持封装的边界.
      • 事务回滚;
      • 棋类游戏中的悔棋;
      • PhotoShop的历史记录.
  • 相关模式

    • 命令模式: 如果在使用命令模式时需要实现命令的撤销, 那么可用Memento来存储可撤销的状态.
    • 迭代器模式: 备忘录可用于迭代.

参考:
设计模式: 可复用面向对象软件的基础
大话设计模式
高淇讲设计模式
《JAVA与模式》之备忘录模式
23种设计模式(15):备忘录模式

  • by 攻城师@翡青
    • Email: feiqing.zjf@gmail.com
    • 博客: 攻城师-翡青 - http://blog.csdn.net/zjf280441589
    • 微博: 攻城师-翡青 - http://weibo.com/u/3319050953

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

备忘录模式 的相关文章

随机推荐

  • 智能家居HomeAssistant(二)Docker安装homeassistant

    目录 1 查找镜像 2 拉取镜像 3 查看镜像 4 启动镜像 5 查看容器运行状态 6 开机自动启动配置 7 自动重新启动配置 扩展 xff1a 下一篇MQTT配置 xff1a 智能家居HomeAssistant xff08 三 xff09
  • defined but not used [-Wunused-function] 使用 __attribute__((unused)) 告诉编译器忽略此告警

    在C程序中 xff0c 如果定义了一个静态函数或变量 xff0c 而没有去使用 xff0c 编译时会有一个警告 xff1a 39 at wait send finish 39 defined but not used Wunused fun
  • 【QT】串口调试助手(串口编程代码详解)

    目录 1 串口助手最终成品效果 1 1 串口功能描述 1 2 串口接收数据显示图 1 3 串口发送数据显示图 2 项目编程代码详解 2 1 项目框架 2 2 工程配置 serial pro 2 3 设计布局 serial ui 2 4 串口
  • 【Linux-ARM】安装海思himix200交叉编译openssl-arm静态库与动态库

    目录 一 安装海思himix200交叉编译工具链 1 编译环境 编辑 编辑 2 安装说明 3 配置参数 4 编译 5 查看环境变量 6 查看安装后版本 二 arm himix200 linux 交叉编译openssl arm静态库与动态库
  • 【Linux-ARM】电脑 WiFi 上网,开发板与电脑直连

    目录 1 准备工作 2 VMware 设置 3 Ubuntu 设置 4 Windows 设置 5 开发板设置 6 ping 测试 7 小结 1 准备工作 使用场景 xff1a 路由器离电脑比较远 xff0c 只有一条网线 设备 xff1a
  • 【Linux-ARM】arm-sgmstar-gnueabihf-9.1.0-202007-gcc交叉编译openssl、curl静态库.a与动态库.so

    编译环境 xff1a ubuntu 18 04 一 安装交叉编译器arm sgmstar gnueabihf 9 1 0 202007 gcc 1 下载交叉编译包 ARM交叉编译器的免安装解压包链接 xff1a ARM交叉编译器 xff1a
  • Python安装第三方库PIL时失败的解决办法

    Python中 xff0c 安装第三方模块 xff0c 是通过setuptools这个工具完成的 Python有两个封装了setuptools的包管理工具 xff1a easy install和pip 目前官方推荐使用pip 安装一个第三方
  • 小程序游戏开发一般多少钱?游戏小程序开发制作

    小程序游戏开发一般多少钱 xff1f 游戏小程序开发制作 有网友咨询我们小程序游戏开发一般多少钱 xff1f 关于游戏小程序开发制作 xff0c 我们公司涉及的开发案例比较少 xff0c 开发游戏小程序的成本要比商城小程序 xff0c 或者
  • MySQL学习笔记_9_MySQL高级操作(上)

    MySQL 高级操作 xff08 上 xff09 一 MySQL 表复制 create table t2 like t1 复制表结构 xff0c t2 可以学习到 t1 所有的表结构 insert into t2 select from t
  • MySQL学习笔记_10_MySQL高级操作(下)

    MySQL 高级操作 xff08 下 xff09 五 MySQL 预处理语句 1 设置预处理 stmt xff0c 传递一个数据作为 where 的判断条件 prepare stmt from select from table name
  • Linux下的tree命令 --Linux下目录树查看

    Linux下的tree命令 Linux下目录树查看 有时我们需要生成目录树结构 可以使用的有ls R 但是实际效果并不好 这时需要用到tree命令 但是大部分Linux系统是默认不安装该命令的 需要自己安装一下 tree的常见用法 tree
  • gcc学习(一)[第二版]

    gcc简介 1 gcc是GNU Compiler Collection的缩写 最初是作为C语言的编译器 xff08 GNU C Compiler xff09 作者为Richard Stallman xff0c 是GNU项目的奠基者 现在已经
  • Socket编程实践(9) --套接字IO超时设置方法

    引 超时设置3种方案 1 alarm超时设置方法 代码实现 这种方式较少用 void sigHandlerForSigAlrm int signo return signal SIGALRM sigHandlerForSigAlrm ala
  • 岁月划过生命线(从0到阿里)

    从来没有想到自己的求职之路会这么顺利 第一次投阿里就拿到了offer 以前一直都是做好被刷的准备的 3月31号晚上收到了来自阿里的正式offer 签下录取意向书 粗略算了一下 从2012年9月份正式入学进入计算机系到2015年3月签下阿里o
  • MyBatis 实践 -Mapper与DAO

    MyBatis 实践 标签 xff1a Java与存储 MyBatis简介 MyBatis前身是iBatis 是一个基于Java的数据持久层 对象关系映射 ORM 框架 MyBatis是对JDBC的封装 使开发人员只需关注SQL本身 而不需
  • Maven 核心原理

    Maven 核心原理 标签 xff1a Java基础 Maven 是每一位Java工程师每天都会接触的工具 但据我所知其实很多人对Maven理解的并不深 只把它当做一个依赖管理工具 下载依赖 打包 Maven很多核心的功能反而没用上 最近重
  • JVM初探 -JVM内存模型

    JVM初探 JVM内存模型 标签 xff1a JVM JVM是每个Java开发每天都会接触到的东西 其相关知识也应该是每个人都要深入了解的 但接触了很多人发现 或了解片面或知识体系陈旧 因此最近抽时间研读了几本评价较高的JVM入门书籍 算是
  • Docker: USER 指定当前用户

    Docker USER 指定当前用户 格式 xff1a USER lt 用户名 gt USER 指令和 WORKDIR 相似 xff0c 都是改变环境状态并影响以后的层 WORKDIR 是改变工作目录 xff0c USER 则是改变之后层的
  • 岁月划过生命线(2016 年终总结 -季度之星)

    岁月划过生命线 2016 年终总结 季度之星 标签 xff1a coder 年假结束 明天就要回到杭州 回到我fighting的战场 回首过去的2016 放纵了许多 但也收获了很多 n个项目 n个框架 第一个季度之星 头像第一次登上CSDN
  • 备忘录模式

    备忘录模式 标签 xff1a Java与设计模式 备忘录模式 在不破坏封装性的前提下 捕获一个对象的内部状态 or 拷贝 并在该对象之外保存这个状态 这样以后就可 将该对象恢复到原先保存的状态 图片来源 设计模式 可复用面向对象软件的基础