行为型设计模式——备忘录模式

备忘录模式

备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。定义: 又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。

备忘录模式的主要角色如下:

  • 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  • 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  • 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

备忘录有两个等效的接口:

  • 窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。
  • 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。

案例实现

【例】游戏存档功能

游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。

要实现上述案例,有两种方式:

  • “白箱”备忘录模式
  • “黑箱”备忘录模式
“白箱”备忘录模式

备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。类图如下:
在这里插入图片描述
其实上面的RoleStateCaretaker类可以不要,它其实就维护了一个RoleStateMemento。其实RoleStateMemento就是我们所谓的数据对象,存储游戏中的关键数据,当需要存档时将游戏数据保存到一个新RoleStateMemento对象即可。代码如下:

首先得定义游戏角色类:

// 定义游戏角色类
public class GameRole {
    private int blood; //血量生命
    private int atk;  //攻击力
    private int def;  //防御力

    // 初始化状态
    public void initState(){
        this.blood =100;
        this.atk = 100;
        this.def = 100;
    }
    // 保存角色状态
    public RoleStateMemento saveState(){
        return new RoleStateMemento(blood,atk,def);
    }

    // 加载角色状态
    public void recoverState(RoleStateMemento roleStateMemento) {
        this.blood = roleStateMemento.getBlood();
        this.atk = roleStateMemento.getAtk();
        this.def = roleStateMemento.getDef();
    }

    // 显示角色状态
    public void showRoleState(){
        System.out.println("角色生命力:" + blood);
    }

    public void setBlood(int val){
        this.blood = val;
    }
}

然后我们定义游戏角色的关键信息数据对象:

public class RoleStateMemento {
    private int blood; //血量生命
    private int atk;  //攻击力
    private int def;  //防御力

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

    public int getBlood() {
        return blood;
    }

    public int getAtk() {
        return atk;
    }

    public int getDef() {
        return def;
    }
}

然后再客户类中测试如下:

public class Main {
    public static void main(String[] args) {
        // 开始游戏
        GameRole role = new GameRole();
        role.initState();
        role.showRoleState();

        System.out.println("--------打boss受伤...");
        // 打boss,只剩下30血量
        role.setBlood(30);
        role.showRoleState();

        // 存档数据
        System.out.println("--------游戏存档...");
        RoleStateMemento roleStateMemento = role.saveState();

        // 继续战斗
        System.out.println("--------继续打boss");
        role.setBlood(0);
        role.showRoleState();
        System.out.println("--------战斗死亡!");

        // 恢复存档
        System.out.println("--------恢复存档...");
        role.recoverState(roleStateMemento);
        role.showRoleState();
    }
}

输出结果:

角色生命力:100
--------打boss受伤…
角色生命力:30
--------游戏存档…
--------继续打boss
角色生命力:0
--------战斗死亡!
--------恢复存档…
角色生命力:30

但是上面的实现就是非常不符合面向对象程序设计的封装性原则,因为游戏的数据对象RoleStateMemento直接暴露给调用者了,下面使用黑盒模式进行改进。

“黑箱”备忘录模式

备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。将 RoleStateMemento 设为 GameRole 的内部类,从而将 RoleStateMemento 对象封装在 GameRole 里面;在外面提供一个标识接口 MementoRoleStateCaretaker 及其他对象使用。这样 GameRole 类看到的是 RoleStateMemento 所有的接口,而RoleStateCaretaker 及其他对象看到的仅仅是标识接口 Memento 所暴露出来的接口,从而维护了封装型。类图如下:

在这里插入图片描述

同样,RoleStateCaretaker类也是可以不需要的,首先定义Memento接口,这个接口只是为了使用多态功能,即传参使用Memento来保护不直接使用ROleStateMemento泄露数据的风险,因为调用方拿到了Memento没有用。代码如下:

public interface Memento {
}

首先对GameRole类进行改造,将数据对象封装到内部类中,代码如下:

public class GameRole {
    private int blood; //血量生命
    private int atk;  //攻击力
    private int def;  //防御力

    // 初始化状态
    public void initState(){
        this.blood =100;
        this.atk = 100;
        this.def = 100;
    }
    // 保存角色状态
    public RoleStateMemento saveState(){
        return new RoleStateMemento(blood,atk,def);
    }

    // 加载角色状态
    public void recoverState(Memento memento) {
        // 要使用实现类必须强转
        RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
        this.blood = roleStateMemento.getBlood();
        this.atk = roleStateMemento.getAtk();
        this.def = roleStateMemento.getDef();
    }

    // 显示角色状态
    public void showRoleState(){
        System.out.println("角色生命力:" + blood);
    }

    public void setBlood(int val){
        this.blood = val;
    }


    // 私有内部类,杜绝外部访问此类
    private class RoleStateMemento implements Memento{
        private int blood; //血量生命
        private int atk;  //攻击力
        private int def;  //防御力

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

        public int getBlood() {
            return blood;
        }

        public int getAtk() {
            return atk;
        }

        public int getDef() {
            return def;
        }
    }

}

如此一来,调用者就无法获取到实际的数据对象,而只能获取他们的接口,然而接口是无法调用的,代码如下:

public class Main {
    public static void main(String[] args) {
        // 开始游戏
        GameRole role = new GameRole();
        role.initState();
        role.showRoleState();

        System.out.println("--------打boss受伤...");
        // 打boss,只剩下30血量
        role.setBlood(30);
        role.showRoleState();

        // 存档数据
        System.out.println("--------游戏存档...");
        // 如果没有Memento接口下面这个肯定报错,因为内部类是私有的
        // GameRole.RoleStateMemento roleStateMemento = role.saveState();
        Memento memento = role.saveState(); // 多态牛逼

        // 继续战斗
        System.out.println("--------继续打boss");
        role.setBlood(0);
        role.showRoleState();
        System.out.println("--------战斗死亡!");

        // 恢复存档
        System.out.println("--------恢复存档...");
        role.recoverState(memento);
        role.showRoleState();
    }
}

输出结果:

角色生命力:100
--------打boss受伤…
角色生命力:30
--------游戏存档…
--------继续打boss
角色生命力:0
--------战斗死亡!
--------恢复存档…
角色生命力:30

优点

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
  • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

缺点

  • 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

使用场景

  • 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。

  • 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,idea等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。

参考内容:

传智播客设计模式相关笔记(主要)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/318121.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ceph入门到精通-通过 CloudBerry Explorer 管理对象bucket

简介 CloudBerry Explorer 是一款可用于管理对象存储(Cloud Object Storage,COS)的客户端工具。通过 CloudBerry Explorer 可实现将 COS 挂载在 Windows 等操作系统上,方便用户访问、移动和管理 COS 文件。 支持系统 支持 Wind…

【动态规划】【滑动窗口】C++算法:3003 执行操作后的最大分割数量

作者推荐 【动态规划】【字符串】扰乱字符串 本文涉及的基础知识点 C算法:滑动窗口总结 动态规划 LeetCode3003 执行操作后的最大分割数量 给你一个下标从 0 开始的字符串 s 和一个整数 k。 你需要执行以下分割操作,直到字符串 s 变为 空&#xff1…

如何开发测试框架?

基本概念 库 英文单词叫Library,库是由代码集合成的一个产品,供程序员调用。面向对象的代码组织形成的库叫类库,面向过程的代码组织形成的库叫函数库。 框架 英文单词叫Framework,框架是为解决一个或一类问题而开发的产品&#x…

【问题探讨】基于非支配排序的蜣螂优化算法NSDBO求解微电网多目标优化调度研究

目录 主要内容 模型研究 结果一览 下载链接 主要内容 该模型以环境保护成本和运行成本为双目标构建了微电网优化调度模型,模型目标函数和约束条件复现文献《基于改进粒子群算法的微电网多目标优化调度》,程序的特点是采用非支配排序的蜣螂…

Flutter-Web从0到部署上线(实践+埋坑)

本文字数:7743字 预计阅读时间:60分钟 01 前言 首先说明一下,这篇文章是给具备Flutter开发经验的客户端同学看的。Flutter 的诞生虽然来自 Google 的 Chrome 团队,但大家都知道 Flutter 最先支持的平台是 Android 和 iOS&#xff…

挖种子小游戏

欢迎来到程序小院 挖种子 玩法&#xff1a;看到种子点击鼠标左键进行挖种子&#xff0c;30秒内看你能够挖多少颗种子&#xff0c;快去挖种子吧^^。开始游戏https://www.ormcc.com/play/gameStart/251 html <canvas id"canvas" width"640" height"…

Docker五部曲之三:镜像构建

文章目录 前言Docker构建架构构建指令构建上下文本地目录Git存储库压缩文件纯文本文件.dockerignore文件 Dockerfile解析器指令环境变量命令执行格式exec格式shell格式 FROMRUNCMDLABELEXPOSEENVADDCOPYENTRYPOINTVOLUMEUSERWORKDIRARGONBUILDSHELL 多级构建 前言 本文均翻译自…

每日一题——LeetCode1103.分糖果 ||

方法一 个人方法&#xff1a; 有多少人就创建多大的数组并把数组的所有元素初始化为0&#xff0c;只要还有糖果&#xff0c;就循环给数组从头到尾添加糖果&#xff0c;每次分的糖果数递增1&#xff0c;最后可能刚好分完也可能不够&#xff0c;不够就还剩多少给多少。 var dis…

11Spring IoC注解式开发(下)(负责注入的注解/全注解开发)

1负责注入的注解 负责注入的注解&#xff0c;常见的包括四个&#xff1a; ValueAutowiredQualifierResource 1.1 Value 当属性的类型是简单类型时&#xff0c;可以使用Value注解进行注入。Value注解可以出现在属性上、setter方法上、以及构造方法的形参上, 方便起见,一般直…

【 ATU 随笔记 - Inverter 】PV Inverter 太阳能逆变器市场分析

一、简介 在上一篇的介绍中与大家分享了Micro Inverter ( 微型逆变器 )的用途与特色&#xff0c;也提到 Micro Inverter 适合家庭或是一些小型企业的需求。太阳能作为再生能源的代表&#xff0c;在当今能源转型中扮演着重要角色&#xff0c;也是有大型企业、大型能源站的需求&a…

Java项目:06 Springboot的进销存管理系统

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 进销存管理系统 介绍 进销存系统是为了对企业生产经营中进货、出货、批发销售、付款等全程进行&#xff08;从接获订单合同开 始&#xff0c;进入物料采购、入…

杨中科 .NETCORE ENTITY FRAMEWORK CORE-1 EFCORE 第一部分

一 、什么是EF Core 什么是ORM 1、说明: 本课程需要你有数据库、SOL等基础知识。 2、ORM: ObjectRelational Mapping。让开发者用对象操作的形式操作关系数据库 比如插入: User user new User(Name"admin"Password"123”; orm.Save(user);比如查询: Book b…

计算机三级(网络技术)一综合题(IP地址计算)

例题一 &#xff08;正常算&#xff09; 计算并填写下表 地址类别 A类地址段是1.0.0.0~127.255.255.255 1~127 B类地址段是128.0.0.0~191.255.255.255 128~191 C类地址段是192.0.0.0~223.255.255.255 192~223 所以41填A 网络地址为主机位全0 根据子网掩码&…

Redis实现分布式会话

Redis实现分布式会话 1 什么是分布式会话 1 这是我么之前学过的注册登录模式 2 如果非常多的人访问&#xff0c;因为单台服务器的访问承受能力是有限的&#xff0c;那么我们就想用多态服务器来承担压力 3 一般通过负载均衡的方式来实现&#xff0c;来分担服务器的压力。 4 负…

【Redis】Redis数据过期策略、数据淘汰策略

数据过期策略 首先&#xff0c;我们要知道Redis的数据过期策略是惰性删除和定期删除结合使用。 面试题&#xff1a; 惰性删除 定期删除 数据淘汰策略 Redis支持8种数据淘汰策略&#xff1a; noeviction&#xff1a;不淘汰任何key&#xff0c;当内存满时&#xff0c;不写入任何…

TCP之三次握手四次挥手与UDP区别

文章目录 1 TCP三次握手四次挥手1.1 数据包说明1.1.1 TCP数据包1.1.2 UDP数据包1.1.3 TCP和UDP差异1.1.4 TCP可靠性传输机制 1.2 三次握手1.2.1 三次握手定义1.2.2 三次握手问题1.2.2.1 问题引入分析1.2.2.2 历史连接1.2.2.3 同步双方初始序列号1.2.2.4 避免资源浪费 1.3 四次挥…

POSTGRESQL中ETL、fdw的平行替换

POSTGRESQL中ETL、fdw的平行替换 01、简介 “ 在我前两次的文章中&#xff0c;说到postgresql对于python的支持&#xff0c;其实很多功能也就可以封装进入的postgresql数据库中去。比如fdw、etl等&#xff0c;本文将以此为叙述点&#xff0c;进行演示展示” 在postgresql数据…

详解矩阵变换:伸缩,旋转,反射和投影

目录 一. 矩阵子空间 二. 矩阵变换 2.1 伸缩矩阵 2.2 旋转矩阵 2.3 反射矩阵 2.4 投影矩阵 2.5 小结 三. 矩阵变换与函数 3.1 原点 3.2 常数倍性质 3.3 加法性质 3.4 小结 四. 空间变换 五. 小结 一. 矩阵子空间 矩阵与向量相乘Ax可以看成子空间的变换。 零空间…

一文搞定,JMeter的三种参数化方式

1、Test Plan 中添加变量 可以在 Test Plan 中设置好添加变量&#xff0c;变量名可以在任意的位置使用&#xff0c;比如说在线程组中直接用${ 变量名 }方式引用&#xff0c;步骤如下&#xff1a; 1&#xff09;设置变量名和变量值 2&#xff09;添加线程组 3&#xff09;添加…

[情商-11]:人际交流的心理架构与需求层次模型

目录 前言&#xff1a; 一、心理架构 1.1 个体生理层 1.2 个体心理层 1.3 点对点人际交流层 1.4 社会网络层 1.5 社会价值层 二、人的需求层次模型 2.1 需求&#xff08;欲望&#xff09;层次模型 2.2 基因与人需求之间的关系 2.3 个体生理需求 2.4 个体的心理需求…