行为型模式--备忘录模式

news2024/10/7 2:22:08

目录

概述

结构

案例实现

“白箱”备忘录模式

总结:

 “黑箱”备忘录模式

优缺点

优点:

缺点:

使用场景


概述

又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态, 以便以后当需要时能将该对象恢复到原先保存的状态。

备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状 态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销 (Undo)操作。

如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销 当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩 游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。

结构

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

发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据 的功能,实现其他业务功能,它可以访问备忘录里的所有信息。

备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起 人。

管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备 忘录的内容进行访问与修改。

备忘录有两个等效的接口

窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录 的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。

宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象 的内部状态。

案例实现

【例】游戏挑战BOSS

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

要实现上述案例,有两种方式: “白箱”备忘录模式 “黑箱”备忘录模式

“白箱”备忘录模式

备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公 开。类图如下

 代码如下:

游戏角色类属于发起人角色

public class GameRole {
    private int vit; //生命力
    private int atk; //攻击力
    private int def; //防御力
    //初始化状态
    public void initState() {
        this.vit = 100;
        this.atk = 100;
        this.def = 100;
    }
    //战斗
    public void fight() {
        this.vit = 0;
        this.atk = 0;
        this.def = 0;
    }
    //保存角色状态
    public RoleStateMemento saveState() {
        return new RoleStateMemento(vit, atk, def);
    }
    //回复角色状态
    public void recoverState(RoleStateMemento roleStateMemento) {
        this.vit = roleStateMemento.getVit();
        this.atk = roleStateMemento.getAtk();
        this.def = roleStateMemento.getDef();
    }
    public void stateDisplay() {
        System.out.println("角色生命力:" + vit);
        System.out.println("角色攻击力:" + atk);
        System.out.println("角色防御力:" + def);
    }
}

游戏状态存储类(备忘录类)

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 class RoleStateCaretaker {
    private RoleStateMemento roleStateMemento;
    public RoleStateMemento getRoleStateMemento() {
        return roleStateMemento;
    }
    public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
        this.roleStateMemento = roleStateMemento;
    }
}

测试类

public class Client {
    public static void main(String[] args) {
        System.out.println("------------大战Boss前------------");
        //大战Boss前
        GameRole gameRole = new GameRole();
        gameRole.initState();
        gameRole.stateDisplay();
        //保存进度
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
        System.out.println("------------大战Boss后------------");
        //大战Boss时,损耗严重
        gameRole.fight();
        gameRole.stateDisplay();
        System.out.println("------------恢复之前状态------------");
        //恢复之前状态
        gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
        gameRole.stateDisplay();
    }
}

总结:

白箱备忘录模式是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式 的大部分用意。

 “黑箱”备忘录模式

备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重 接口的办法就是将备忘录类设计成发起人类的内部成员类。 将 RoleStateMemento 设为 GameRole 的内部类,从而将 RoleStateMemento 对象封装在 GameRole 里面;

在外面提供一个标识接口 Memento 给 RoleStateCaretaker 及其他对象使用。 这样 GameRole 类看到的是 RoleStateMemento 所有的接口,而 RoleStateCaretaker 及其他对 象看到的仅仅是标识接口 Memento 所暴露出来的接口,从而维护了封装型。类图如下:

窄接口 Memento

public interface Memento {
}

定义发起人类 GameRole ,并在内部定义备忘录内部类 RoleStateMemento (该内部类设置为私有 的)

/游戏角色类
public class GameRole {
    private int vit; //生命力
    private int atk; //攻击力
    private int def; //防御力
    //初始化状态
    public void initState() {
        this.vit = 100;
        this.atk = 100;
        this.def = 100;
    }
    //战斗
    public void fight() {
        this.vit = 0;
        this.atk = 0;
        this.def = 0;
    }
    //保存角色状态
    public Memento saveState() {
        return new RoleStateMemento(vit, atk, def);
    }
    //回复角色状态
    public void recoverState(Memento memento) {
        RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
        this.vit = roleStateMemento.getVit();
        this.atk = roleStateMemento.getAtk();
        this.def = roleStateMemento.getDef();
    }
    public void stateDisplay() {
        System.out.println("角色生命力:" + vit);
        System.out.println("角色攻击力:" + atk);
        System.out.println("角色防御力:" + def);
    }
    private class RoleStateMemento implements Memento {
        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;
        }
    }
}

负责人角色类 RoleStateCaretaker 能够得到的备忘录对象是以 Memento 为接口的,由于这个接 口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容

public class RoleStateCaretaker {
    private Memento memento;
    public Memento getMemento() {
        return memento;
    }
    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}

优缺点

优点:

提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。

实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。

简化了发起人类。

发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录 中,并由管理者进行管理,这符合单一职责原则。

缺点:

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

使用场景

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

zeromq的学习笔记

ctx_t 在创建ctx_t时,会设置以下参数 _io_thread_count io线程数默认是1 _max_sockets最大socket数是1023 _starting标识设置为true,此时socket还没有创建 _terminating设置为false,在调用zmq_ctx_term时该标识会设置为true _tag设置为ZMQ_CTX_TAG_VALUE_GOOD&…

mySql和VSC++

确认主机服务里的mysql服务已打开 使用组合键“winR”运行“services.msc”,进入本地服务窗口; 2.进入本地服务窗口后,在右侧服务列表中,查找到“ mysql ”服务选项; 3.查找到mysql服务选项后,双击打开mysq…

C++ 面向对象(3)——重载运算符和重载函数

C 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。 当您调用一个重…

FDM3D打印系列——3、常用打印材料介绍

大家好,我是阿赵。 FDM3D打印机一般都可以支持多种打印材料的,下面来介绍一下几种常用的打印材料 一、PLA 使用FDM打印,最常见的材料就是PLA了 PLA(Polylactic acid),中文名为生物降解塑料聚乳酸&#…

网络安全面试题,渗透测试面试总结

1.什么是WebShell? WebShell就是以asp、php、jsp或者cgi等网页文件形式存在的─种命令执行环境,也可以将其称做为─种网页后门。黑客在入侵了─个网站后,通常会将这些asp或php后门文件与网站服务器WEB目录下正常的网页文件混在─起,然后就可…

【Vue3】生命周期(钩子)函数

在 Vue 3 中,生命周期函数已经被重新设计为钩子函数,并且与 Vue 2 中的生命周期函数有所不同,可以在 setup 函数中使用 onXXX 形式的钩子函数来执行对应的操作。以下是选项式 API 和组合式 API 中常用的几个钩子函数对比: beforeC…

【Java】PriorityQueue--优先级队列

目录 一、优先级队列 (1)概念 二、优先级队列的模拟实现 (1)堆的概念 (2)堆的存储方式 (3)堆的创建 堆向下调整 (4)堆的插入与删除 堆的插入 堆的…

Kubernetes(k8s)容器编排组件介绍

目录 1 整体架构1.1 Master 架构1.2 Node 架构 2 k8s部署组件介绍2.1 K8s 集群架构图2.2 k8s控制组件2.2.1 控制平面2.2.2 kube-apiserver2.2.3 kube-scheduler2.2.4 kube-controller-manager2.2.5 etcd 2.3 k8s运行组件2.3.1 k8s节点2.3.2 容器集2.3.3 容器运行时引擎2.3.4 ku…

机试复试准备中--梦校真题

一、矩阵转置二、统计单词写法一:读取一整行写法二:依次读入每一个单词 三、二叉排序树(DFS)四、IP地址五、特殊排序六、ab(高精度加法)七、奇偶校验八、最大的两个数九、二叉树遍历(DFS)十、成绩排序十一、…

【C++学习】C++入门 | 引用 | 引用的底层原理 | auto关键字 | 范围for(语法糖)

写在前面: 上一篇文章我介绍了缺省参数和函数重载, 探究了C为什么能够支持函数重载而C语言不能, 这里是传送门,有兴趣可以去看看:http://t.csdn.cn/29ycJ 这篇我们继续来学习C的基础知识。 目录 写在前面&#x…

正交编码与正交沃尔什函数详解

本专栏包含信息论与编码的核心知识,按知识点组织,可作为教学或学习的参考。markdown版本已归档至【Github仓库:https://github.com/timerring/information-theory 】或者公众号【AIShareLab】回复 信息论 获取。 文章目录 正交编码正交编码的…

Spring Boot 集成 WebSocket 实现服务端推送消息到客户端

假设有这样一个场景:服务端的资源经常在更新,客户端需要尽量及时地了解到这些更新发生后展示给用户,如果是 HTTP 1.1,通常会开启 ajax 请求询问服务端是否有更新,通过定时器反复轮询服务端响应的资源是否有更新。 在长…

css基础(二)

目录 1. CSS 的复合选择器 1.1 什么是复合选择器 1.2 后代选择器(重要) 1.3 子选择器(重要) 1.4 并集选择器(重要) 1.5 伪类选择器 1.6 链接伪类选择器 1.7 :focus伪类选择器 1.8 复合选择器总结 二、 CSS 的元素显示模式 2.1什么是元素显示模…

多线程编程和并行计算的实例:期货交易及打车软件算法

多线程编程和并行计算的实例:期货交易及打车软件算法 解决现实生活中的问题时,多处理器和多核系统的普及使并行计算成为一个关键的性能提升手段。在这篇博客中,我们将通过深入讨论两个引人入胜而又具有实际意义的场景——期货交易和打车匹配算法&#xf…

CSS圆角进化论

CSS圆角发展过程 大致经历了3个阶段,包括: 背景图片实现圆角CSS2.0标签模拟圆角CSS3.0圆角属性(border-radius属性)实现圆角 ☛背景图片实现圆角:使用背景图片实现圆角的方式很多,实现的方式和圆角的切图方式关系密…

AI绘图软件分享:Midjourney 基础教程(三)

大家好,我是权知星球,今天继续给大家分享Midjourney 基础教程(三):Midjourney 图生图。 刚开始学习使⽤ AI 绘画时,⼤部分⼈的绘画⽅式: 有⼀个想象中的画⾯,⽤中⽂将这个画⾯描述…

【文件操作与IO】Java中如何操作文件

目录 Java 中操作文件 File 概述 属性 构造方法 方法 代码示例 文件内容的读写 —— 数据流 InputStream 概述 FileInputStream 概述 利用 Scanner 进行字符读取 OutputStream 概述 利用 OutputStreamWriter 进行字符写入 利用 PrintWriter 找到我们熟悉的方法 代码…

D. A Wide, Wide Graph(树的直径)

Problem - 1805D - Codeforces 给定一棵包含n个节点的树(一个无环联通图),对于一个固定的整数k,定义Gk为一个具有n个节点的无向图,其中只有当在给定树中节点u和v之间的距离至少为k时才存在边。 对于从1到n的每个k&…

undefined reference to `tputs‘

昨天在Debian11 上编译 bluez 的时候,看这里,出现了如下这个错误,一般这种未定义的错误提示都是没有链接正确的库导致,但是搞了很久都没解决。 奇怪的是之前在 Centos 上编译却没有遇到这个问题,而且在 configure 时也…

做Java开发,真的“穷途末路”了吗?浅谈从2018-2023年,这行到底“卷”成了啥样

文章目录 一、火爆的行业1、裁员潮引发的行业惶恐2、国情下的行业现状3、时代的快速发展 二、Java开发“卷”成了啥样1、2013年2、2018年3、2013年4、真的需要这么多知识吗 三、大龄程序员何去何从引用来处 一、火爆的行业 “程序员”这个代名词,似乎总是跟“高薪”…