Java 设计模式——备忘录模式

news2024/11/19 2:22:21

目录

  • 1.概述
  • 2.结构
  • 3.案例实现
    • 3.1.“白箱”备忘录模式
    • 3.2.”黑箱”备忘录模式
  • 4.优缺点
  • 5.使用场景

1.概述

(1)备忘录模式 (Memento Pattern) 又称为快照模式,是一种行为型设计模式,它提供了一种保存和恢复对象状态的机制。备忘录模式允许在不破坏封装性的前提下,捕获一个对象的内部状态,并将其保存在一个备忘录 (Memento) 对象中。随后,可以使用备忘录对象将对象状态恢复到之前保存的状态。

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

2.结构

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

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

(2)除此之外,备忘录有两个等效的接口:

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

3.案例实现

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

3.1.“白箱”备忘录模式

备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。类图如下:
在这里插入图片描述
具体实现代码如下:
RoleStateMemento.java(备忘录角色)

//备忘录角色类(存储历史状态)
public class RoleStateMemento {
    //生命力
    private int vit;
    //攻击力
    private int atk;
    //防御力
    private int def;
    
    public RoleStateMemento() {
    }
    
    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;
    }
}

GameRole.java(发起人角色)

//游戏角色类(发起人角色)
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 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;
    }
}

RoleStateCaretaker.java(管理者角色)

//备忘录对象管理对象
public class RoleStateCaretaker {
    //声明RoleStateMemento类型的变量
    private RoleStateMemento roleStateMemento;
    
    public RoleStateMemento getRoleStateMemento() {
        return roleStateMemento;
    }
    
    public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
        this.roleStateMemento = roleStateMemento;
        //白箱
        //roleStateMemento.setAtk(100);
    }
}

Client.java

public class Client {
    public static void main(String[] args) {
        System.out.println("---------------大战 Boss 前-----------------");
        //创建游戏角色对象
        GameRole gameRole = new GameRole();
        //初始化状态操作
        gameRole.initState();
        gameRole.stateDisplay();
    
        //将该游戏角色内部状态进行备份
        //创建管理者对象
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
    
        System.out.println("---------------大战 Boss 后-----------------");
        //损耗严重
        gameRole.fight();
        gameRole.stateDisplay();
    
        System.out.println("---------------恢复之前的状态-----------------");
        gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
        gameRole.stateDisplay();
    }
}

结果如下:

---------------大战 Boss-----------------
角色生命力:100
角色攻击力:100
角色防御力:100
---------------大战 Boss-----------------
角色生命力:0
角色攻击力:0
角色防御力:0
---------------恢复之前的状态-----------------
角色生命力:100
角色攻击力:100
角色防御力:100

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

3.2.”黑箱”备忘录模式

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

在这里插入图片描述

具体实现代码如下:
Memento.java(备忘录接口)

//备忘录接口,对外提供窄接口
public interface Memento {
}

GameRole.java(发起人角色)

//游戏角色类(属于发起人角色)
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);
    }

    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;
    }

	//在内部定义备忘录内部类 RoleStateMemento (该内部类设置为私有的)
    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;
        }

        public RoleStateMemento() {
        }

        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;
        }
    }
}

RoleStateCaretaker.java(管理者角色)

//备忘录对象管理对象
public class RoleStateCaretaker {

	/*
		负责人角色类 RoleStateCaretaker 能够得到的备忘录对象是以 Memento 为接口的,由于
		这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容。
	*/
    //声明RoleStateMemento类型的变量
    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}
public class Client {
    public static void main(String[] args) {
        System.out.println("---------------大战 Boss 前-----------------");
        //创建游戏角色对象
        GameRole gameRole = new GameRole();
        gameRole.initState();//初始化状态操作
        gameRole.stateDisplay();

        //将该游戏角色内部状态进行备份
        //创建管理者对象
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
        roleStateCaretaker.setMemento(gameRole.saveState());

        System.out.println("---------------大战 Boss 后-----------------");
        //损耗严重
        gameRole.fight();
        gameRole.stateDisplay();

        System.out.println("---------------恢复之前的状态-----------------");
        gameRole.recoverState(roleStateCaretaker.getMemento());
        gameRole.stateDisplay();
    }
}

结果与“黑箱”备忘录模式的一样。

4.优缺点

(1)备忘录模式是一种行为型设计模式,用于捕获和恢复对象的内部状态。备忘录模式具有以下优点和缺点:

  • 优点
    • 备忘录模式可以在不破坏对象封装性的情况下保存和恢复对象的状态。它将状态的获取和修改分离开来,有助于实现对象的单一职责原则。
    • 备忘录模式提供了一种灵活和可扩展的方式来保存和恢复对象状态。可以动态地创建备忘录对象,并将其存储在任何地方,以满足不同的需求。
    • 备忘录模式可以简化备份和还原操作。通过将对象状态保存到备忘录对象中,可以轻松地实现撤销、重做和历史记录等功能。
    • 备忘录模式对客户端透明,客户端无需关心对象状态的保存和恢复过程。这减少了客户端和对象之间的耦合性,提高了代码的可维护性和灵活性。
  • 缺点
    • 备忘录模式可能会增加内存消耗。当对象的状态较大或状态变化频繁时,需要存储大量的备忘录对象,从而增加了内存开销。
    • 备忘录模式的实现可能涉及到对象的深拷贝操作,这可能会导致性能下降。
    • 备忘录模式无法防止其他对象直接访问备忘录对象,从而破坏对象的封装性。需要在设计和实现过程中注意安全性和可靠性。

(2)综上所述,备忘录模式是一种有用的设计模式,可以有效地管理对象的状态。然而,在应用时需要权衡其优点和缺点,并根据具体情况确定是否使用备忘录模式。

5.使用场景

(1)备忘录模式适用于以下场景:

  • 保存和还原对象状态。如文本编辑器中的撤销和重做操作、游戏中的存档和读档等。
  • 需要创建快照的应用程序。比如数据备份、快照生成器等应用场景。
  • 实现草稿箱功能。将用户输入的内容保存到备忘录对象中,用户可以随时从备忘录对象中恢复已保存的内容。
  • 实现事务的回滚功能。比如数据库事务中的回滚操作、操作系统中的系统还原等。
  • 需要保存历史记录的应用程序。比如浏览器中的访问历史记录、绘图工具中的历史记录等。

(2)总的来说,备忘录模式适用于所有需要保存和恢复对象状态的应用场景。备忘录模式可以帮助实现对象的可撤销性、可恢复性和灵活性,并简化备份和还原操作。

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

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

相关文章

Python-图片去重

直接上代码 # 修改一下第34行文件夹路径以及13行图片后缀名即可使用 import os from hashlib import md5def remove_duplicate_images(folder_path):image_files []duplicate_images set()# 遍历文件夹,找到所有 JPG 图片文件for root, dirs, files in os.walk(f…

夜莺专业版网络设备功能介绍

网络设备采集简介 网络设备的问题通常会产生较大范围的影响,因此采集监控网络设备是一项常见的任务。不同公司在实施网络设备采集时可能采用不同的方案,主要有三类: SNMP(Simple Network Management Protocol)&#x…

【源码篇】基于SpringBoot+Vue实现的在线考试系统

文章目录 系统说明技术选型成果展示账号地址及其他说明 系统说明 基于SpringBootVue实现的在线考试系统是为高校打造的一款在线考试平台。 系统功能说明 1、系统共有管理员、老师、学生三个角色,管理员拥有系统最高权限。 2、老师拥有考试管理、题库管理、成绩管…

⭐ Unity + ARKIT 介绍 以及 平面检测的实现

在AR插件中,ARKIT是比较特殊的一个,首先他在很多追踪上的效果要比其他的AR插件要好,但是只能在IOS系统设备上运行。 1.首先ARKIT在最新版Unity已经集成在AR Foundation中,那我们就需要ARSession 和ARSessionOrigin这两个重要组件…

【CMD】工具脚本-输出其他cmd脚本的头部注释

小轮子,用于管理自己的脚本。 如下图的运行结果所示,效果便是输出本工具脚本所在路径下的其他cmd脚本的头部注释(用于提醒健忘的自己曾经写了啥东西。 可以把不时会用得上的个人脚本,集中放进一个文件夹中,然后将这个文件夹的路径…

【C++】赋值运算符重载

💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤 📃个人主页 :阿然成长日记 …

“学习强国”学习达人挑战赛流程

“学习强国”学习达人挑战赛通过赛易知识竞赛云平台完成,完整竞赛流程和规则如下: 比赛形式:“学习强国”学习平台首届达人挑战赛决赛为电视知识竞赛,前期需录入100名选手个人信息与各环节竞赛题库。 比赛载体:手机在…

龙迅#LT6911GX是一款高性能HDMI2.1至MIPI或LVDS芯片,支持图像处理,DSC压缩和嵌入式LPDDR4 旋转功能!

1.描述 应用功能:LT6711GX适用于HDMI2.1转MIPICSI/DSI;HDMI2.1转LVDS,支持高刷模式,带HDCP 方案! 分辨率:最高支持8K30HZ 工作温度范围:−40C to 85C 产品封装:BGA169(9*…

RabbitMQ 的七种消息传递形式

文章目录 一、RabbitMQ 架构简介二、准备工作 三、消息收发1. Hello World2. Work queues3. Publish/Subscrite3.1. Direct3.2. Fanout3.3. Topic3.4. Header 4. Routing5. Topics 大部分情况下,我们可能都是在 Spring Boot 或者 Spring Cloud 环境下使用 RabbitMQ&…

【powerjob】定时任务调度器 xxl-job和powerjob对比

文章目录 同类产品对比资源及部署相关资源占用对比:部署方式:xxl job :调度器:执行器: powerjob:调度器:执行器: 总结 背景: 目前系统的定时任务主要通过Spring框架自带的Scheduled注…

SpringBoot:SpringMVC(上)

文章目录 前言一、SpringMVC是什么?1.1 MVC的定义:1.2 MVC 和 Spring MVC 的关系 二、Spring MVC 创建和连接2.1创建springmvc2.2接下来,创建⼀个 UserController 类,实现⽤户到 Spring 程序的互联互通,具体实现代码如…

分享77个菜单导航JS特效,总有一款适合您

分享77个菜单导航JS特效,总有一款适合您 77个菜单导航JS特效下载 链接:https://pan.baidu.com/s/1sfT9ONLH4ocliA1C7Z5xbQ?pwd6666 提取码:6666 Python采集代码下载链接:采集代码.zip - 蓝奏云 学习知识费力气&#xff0…

数据库系统原理与实践 笔记 #11

文章目录 数据库系统原理与实践 笔记 #11事务管理和并发控制与恢复事务概念转账的例子 事务ACID特性ACID特性事务原子性和持久性事务隔离性调度SQL中的事务定义 可串行化事务的简化视图冲突的指令冲突可串行化 可恢复性级联回滚无级联调度 数据库系统原理与实践 笔记 #11 事务…

Python中format函数用法

嗨喽~大家好呀,这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 format优点 format是python2.6新增的一个格式化字符串的方法,相对于老版的%格式方法,它有很多优点。 1.不需要理会数据类型的问题&#…

pip的基本命令和使用

pip 简介 pip是Python官方的包管理器,可以方便地安装、升级和卸载Python包。 pip 常用命令 显示版本和路径 pip --version获取帮助 pip --help升级pip和升级包 pip install --upgrade pip # Linux/macOS pip install -U pip # windowspip install…

CVE-2016-2510CVE-2017-5586 BeanShell漏洞

前言: 首先我们需要了解BeanShell具体是做什么: BeanShell 是一种轻量级的可嵌入式脚本语言,用于在 Java 环境中执行脚本代码。它提供了一种简单、灵活的方式来扩展和定制 Java 应用程序的行为,允许开发人员动态地执行和评估脚本…

顺序查找、折半查找、分块查找

概念 查找表,分为静态查找表和动态查找表。 顺序查找 效率分析: 优化 折半查找 折半查找,又称“二分查找”仅适用于有序的顺序表。 ⭐,因为顺序表可以随机访问,链表不可以 效率分析 折半查找判定树的构造 如果&…

食品加工厂污水处理设备有哪些

食品加工厂污水处理设备是确保食品生产过程中污水能够得到有效处理和排放的重要设备。目前,针对食品加工厂污水处理的设备包括以下几个主要分类: 1. 预处理设备:包括格栅、沉砂池和沉淀池等。格栅主要用于去除污水中的大颗粒固体物质&#x…

Ubuntu20.04安装向日葵、开机自启、解决windows系统远程黑屏(笔记)

这里写目录标题 动机1. Ubuntu20.04 安装向日葵2. 设置开机自启3. 解决windows不可远程的问题4. 大公告成 动机 办公室有个工作站,要比我的笔记本的CPU稍微好一点,用来跑陆面过程。我信心满满的装了个Ubuntu20.04双系统,但是发现向日葵安装不上了。我少…

一.线性表

一.单链表 1.定义结构体 1.1不带头结点的结构体 typedef struct node{int data;struct node *next;//struct node *prior;//双向链表需要加上指针prior }Lnode,*Linklist; 1.2带头结点的结构体 typedef struct node{int data;struct node * next; }Node;typedef struct l…