行为型设计模式05-备忘录模式

news/2025/2/22 17:57:37

🧑‍💻作者:猫十二懿

❤️‍🔥账号:CSDN 、掘金 、个人博客 、Github

🎉公众号:猫十二懿

备忘录模式

1、备忘录模式介绍

备忘录模式是一种行为型设计模式,用于在不破坏封装性的前提下保存和恢复对象的状态。该模式通常包括三个角色:发起者(Originator)、备忘录(Memento)和管理者(Caretaker)。

1.1 存在问题

如果在工作中不使用备忘录模式,可能会导致一些问题。比如,当我们需要保存程序或对象的某个状态以便在将来某个时刻恢复时,如果我们没有合适的机制来管理状态,那么就很难实现这样的需求。此时,我们可能需要手动记录状态并将其存储到文件或数据库中,同时还需要管理和维护状态的版本控制,这会很耗费时间和精力。而且,如果我们的程序需要支持多种状态的保存和恢复,那么这个过程会变得更加困难。

因此,在这种情况下,使用备忘录模式可以很好地解决这个问题。它提供了一个简单而有效的方法来捕获和存储对象的内部状态,从而使我们可以在需要时轻松地恢复对象的状态。同时,备忘录模式也有助于保持封装性,因为对象的状态仅限于它自己和备忘录对象之间共享,其他对象无法访问或更改状态。

  1. 例如

假设你正在为公司编写一个重要的软件程序,这个程序中包含了一些复杂的业务逻辑。你需要测试和修改这个程序,但是由于代码比较复杂,你很难在第一次测试时分辨出哪些代码有问题,哪些代码没有问题。如果此时你不使用备忘录模式来保存程序的某个状态以便在将来某个时刻回溯到之前的状态,那么你每次都需要重新验证每一段代码的正确性,这会非常浪费时间和精力,并且有可能漏掉一些问题。

另外,如果你在修改程序时不小心破坏了某些核心代码,而此时你没有任何备份或历史记录,那么整个程序就可能遭受损失,这也是没有使用备忘录模式会存在的问题。而如果你使用备忘录模式,就可以轻松地恢复到之前的某个状态,避免了这种风险。

  1. 再例如

假设一个学生正在上课,突然需要离开教室一段时间,但他不希望错过老师所讲解的内容。如果他没有任何方法来记录老师的讲解,那么他在回来之后就无法恢复自己的知识状态,可能会影响以后的学习。但是,如果他能够使用备忘录模式来记录老师的讲解,就可以轻松地恢复到离开教室前的知识状态。

1.2 备忘录模式

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

image-20230508230000293

  1. Originator(发起人):负责创建一个备忘录Memento,用以记录当 前时刻它的内部状态,并可使用备忘录恢复内部状态。Originator 可根据需要决定Memento存储Originator的哪些内部状态。
  2. Memento(备忘录):负责存储Originator对象的内部状态,并可防 止Originator以外的其他对象访问备忘录Memento。备忘录有两个接 口,Caretaker只能看到备忘录的窄接口,它只能将备忘录传递给其 他对象。Originator能够看到一个宽接口,允许它访问返回到先前 状态所需的所有数据。
  3. Caretaker(管理者):负责保存好备忘录Memento,不能对备忘录 的内容进行操作或检查。

1.3 备忘录模式基本代码

备忘录类:

/**
 * @author Shier
 * CreateTime 2023/5/10 11:31
 * 备忘录类
 */
public class Memento {
    private String state;

    public Memento(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}

发起人(初始)类:

/**
 * @author Shier
 * CreateTime 2023/5/10 11:26
 * 发起人(Originator)类
 */
public class Originator {
    /**
     * 状态
     */
    private String state;

    /**
     * 显示数据
     */
    public void show() {
        System.out.println("Current State : " + this.state);
    }

    /**
     * 创建备忘录
     *
     * @return 将当前需要保存的信息导入并实例化出一个Memento对象
     */
    public Memento createMemento(){
        return new Memento(this.state);
    }
    /**
     * 恢复备忘录
     */
    public void recoveryMemento(Memento memento){
        this.setState(memento.getState());
    }

    /**
     * 需要保存的属性
     *
     * @return
     */
    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}

管理类:

/**
 * @author Shier
 * CreateTime 2023/5/10 11:35
 * 管理类
 */
public class Caretaker {
    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}

客户端类:

/**
 * @author Shier
 * CreateTime 2023/5/10 11:36
 */
public class MementoClient {
    public static void main(String[] args) {
        // 原始类初始化
        Originator originator = new Originator();
        originator.setState("ON");
        originator.show();

        // 保存当前状态
        Caretaker caretaker = new Caretaker();
        caretaker.setMemento(originator.createMemento());

        // 状态进行改变
        originator.setState("OFF");
        originator.show();

        // 恢复初始的状态
        originator.recoveryMemento(caretaker.getMemento());
        originator.show();
    }
}

输出结果:

image-20230510114047565

2、具体例子说明

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

初始状态:

image-20230510114351823

战斗后:

image-20230510114430116

2.1 不使用备忘录模式

游戏角色:

/**
 * @author Shier
 * CreateTime 2023/5/10 11:45
 * 游戏角色类
 */
public class GameRole {
    /**
     * 生命力
     */
    private int vitality;

    /**
     * 攻击力
     */
    private int attack;

    /**
     * 防御力
     */
    private int defense;


    /**
     * 状态显示
     */
    public void displayState() {
        System.out.println("角色当前状态:");
        System.out.println("生命力:" + this.vitality);
        System.out.println("攻击力:" + this.attack);
        System.out.println("防御力:" + this.defense);
        System.out.println();
    }

    /**
     * 初始状态
     *
     * @return
     */
    public void getInitState() {
        this.vitality = 100;
        this.attack = 100;
        this.defense = 100;
    }

    /**
     * 战斗后状态
     *
     * @param
     */
    public void fight() {
        this.vitality = 0;
        this.attack = 0;
        this.defense = 0;
    }

    public int getVitality() {
        return vitality;
    }


    public void setVitality(int vitality) {
        this.vitality = vitality;
    }

    public int getAttack() {
        return attack;
    }

    public void setAttack(int attack) {
        this.attack = attack;
    }

    public int getDefense() {
        return defense;
    }

    public void setDefense(int defense) {
        this.defense = defense;
    }
}

客户端:

/**
 * @author Shier
 * CreateTime 2023/5/10 11:51
 */
public class GameClient {
    public static void main(String[] args) {
        // 战斗前
        GameRole gameRole = new GameRole();
        gameRole.getInitState();
        gameRole.displayState();

        // 保存进度
        GameRole backRole = new GameRole();
        backRole.setAttack(gameRole.getAttack());
        backRole.setDefense(gameRole.getDefense());
        backRole.setVitality(gameRole.getVitality());

        // 战斗
        gameRole.fight();
        gameRole.displayState();

        // 恢复战斗前状态
        gameRole.setVitality(backRole.getVitality());
        gameRole.setDefense(backRole.getDefense());
        gameRole.setAttack(backRole.getAttack());
        // 显示恢复后的状态
        gameRole.displayState();
    }
}

输出结果:

image-20230510120726488

分析

确实实现了例子的要求,问题主要在于这客户端的调用。下面的程序存在问题,因为这样写就把整个游戏角色的细节暴露给了客户端,客户端的职责就太大了,需要知道游戏角色的生命力、攻击力、防御力这些细节,还要对它进行’备份’。以后需要增加新的数据,例如增加 ‘魔法力’ 或修改现有的某种力,例如 ‘生命力’ 改为 ‘经验值’,这部分就一定要修改了。同样的道理也存在于恢复时的代码。

image-20230510115755695

解决:

显然,我们希望的是把这些’游戏角色’的存取状态细节封装起来,而且 最好是封装在外部的类当中。以体现职责分离。

2.2 使用备忘录模式实现

代码结构图:

image-20230510115841132

角色状态记录类:

/**
 * @author Shier
 * CreateTime 2023/5/10 11:59
 * 角色状态记录
 */
public class RoleStateMemento {
    /**
     * 生命力
     */
    private int vitality;

    /**
     * 攻击力
     */
    private int attack;

    /**
     * 防御力
     */
    private int defense;

    public RoleStateMemento(int vitality, int attack, int defense) {
        this.vitality = vitality;
        this.attack = attack;
        this.defense = defense;
    }

    public int getVitality() {
        return vitality;
    }


    public void setVitality(int vitality) {
        this.vitality = vitality;
    }

    public int getAttack() {
        return attack;
    }

    public void setAttack(int attack) {
        this.attack = attack;
    }

    public int getDefense() {
        return defense;
    }

    public void setDefense(int defense) {
        this.defense = defense;
    }
}

角色类:

/**
 * @author Shier
 * CreateTime 2023/5/10 11:45
 * 游戏角色类
 */
public class GameRole {
    /**
     * 生命力
     */
    private int vitality;

    /**
     * 攻击力
     */
    private int attack;

    /**
     * 防御力
     */
    private int defense;


    /**
     * 状态显示
     */
    public void displayState() {
        System.out.println("角色当前状态:");
        System.out.println("生命力:" + this.vitality);
        System.out.println("攻击力:" + this.attack);
        System.out.println("防御力:" + this.defense);
        System.out.println();
    }

    /**
     * 初始状态
     *
     * @return
     */
    public void getInitState() {
        this.vitality = 100;
        this.attack = 100;
        this.defense = 100;
    }

    /**
     * 战斗后状态
     *
     * @param
     */
    public void fight() {
        this.vitality = 0;
        this.attack = 0;
        this.defense = 0;
    }

    /**
     * 保存角色状态
     *
     * @return
     */
    public RoleStateMemento savaState(){
        return new RoleStateMemento(this.vitality,this.attack,this.defense);
    }

    /**
     * 恢复角色状态
     * @return
     */
    public void recoveryState(RoleStateMemento roleStateMemento){
        this.setAttack(roleStateMemento.getAttack());
        this.setDefense(roleStateMemento.getDefense());
        this.setVitality(roleStateMemento.getVitality());
    }

    public int getVitality() {
        return vitality;
    }


    public void setVitality(int vitality) {
        this.vitality = vitality;
    }

    public int getAttack() {
        return attack;
    }

    public void setAttack(int attack) {
        this.attack = attack;
    }

    public int getDefense() {
        return defense;
    }

    public void setDefense(int defense) {
        this.defense = defense;
    }
}

角色管理类:

/**
 * @author Shier
 * CreateTime 2023/5/10 12:04
 * 角色管理类
 */
public class RoleStateCaretaker {
    private RoleStateMemento roleStateMemento;

    public RoleStateMemento getRoleStateMemento() {
        return roleStateMemento;
    }

    public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
        this.roleStateMemento = roleStateMemento;
    }
}

客户端类:

/**
 * @author Shier
 * CreateTime 2023/5/10 11:51
 */
public class GameClient2 {
    public static void main(String[] args) {
        // 战斗前
        GameRole role = new GameRole();
        role.getInitState();
        role.displayState();

        // 保存进度
        RoleStateCaretaker caretaker = new RoleStateCaretaker();
        caretaker.setRoleStateMemento(role.savaState());

        // 战斗
        role.fight();
        role.displayState();

        // 恢复战斗前状态
        role.recoveryState(caretaker.getRoleStateMemento());
        // 显示恢复后的状态
        role.displayState();
    }
}

输出结果同上

  1. 把要保存的细节给封装在了RoleStateMemento中了, 哪一天要更改保存的细节也不用影响客户端了。
  2. 使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来
  3. 在当角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原

3、总结

备忘录模式优点:

  1. 可以在不破坏对象封装性的前提下,捕获和恢复对象的内部状态;
  2. 可以对状态进行存储和恢复,保证程序的稳定性;
  3. 可以缩小“黑盒”对象和“白盒”对象之间的耦合度,提高程序的可扩展性和可维护性;
  4. 可以有效地管理多个历史状态,方便用户选择并恢复到特定的状态。

备忘录模式缺点:

  1. 对象状态的保存和恢复会消耗大量的内存,特别是针对大型对象的情况;
  2. 如果状态的保存和恢复涉及到较多的属性(或数据),则备忘录对象可能会非常庞大,导致程序效率低下。

备忘录模式使用场景:

  1. 需要保存和恢复对象状态的情况;
  2. 需要提供撤销操作的情况;
  3. 需要记录对象历史状态的情况;
  4. 需要动态地保存和恢复对象状态的情况;
  5. 功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator可以根据保存的Memento信息还原到前一状态。

to中了, 哪一天要更改保存的细节也不用影响客户端了。

  1. 使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来
  2. 在当角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原

3、总结

备忘录模式优点:

  1. 可以在不破坏对象封装性的前提下,捕获和恢复对象的内部状态;
  2. 可以对状态进行存储和恢复,保证程序的稳定性;
  3. 可以缩小“黑盒”对象和“白盒”对象之间的耦合度,提高程序的可扩展性和可维护性;
  4. 可以有效地管理多个历史状态,方便用户选择并恢复到特定的状态。

备忘录模式缺点:

  1. 对象状态的保存和恢复会消耗大量的内存,特别是针对大型对象的情况;
  2. 如果状态的保存和恢复涉及到较多的属性(或数据),则备忘录对象可能会非常庞大,导致程序效率低下。

备忘录模式使用场景:

  1. 需要保存和恢复对象状态的情况;
  2. 需要提供撤销操作的情况;
  3. 需要记录对象历史状态的情况;
  4. 需要动态地保存和恢复对象状态的情况;
  5. 功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator可以根据保存的Memento信息还原到前一状态。

http://www.niftyadmin.cn/n/437799.html

相关文章

【mysql】1378. 使用唯一标识码替换员工ID

题目: Employees 表: ---------------------- | Column Name | Type | ---------------------- | id | int | | name | varchar | ---------------------- id 是这张表的主键。 这张表的每一行分别代表了某公司其中一位员工的名字和 ID 。 EmployeeUN…

CentOS GCC 离线升级 编译安装 8.3.0

从系统自带的 gcc-4.8.5 版本升级至 gcc-8.3.0 版本 目录 下载源代码: 下载依赖: 编译(约一个小时) 重开控制台确认是否生效 下载源代码: https://ftp.gnu.org/gnu/gcc/gcc-8.3.0/gcc-8.3.0.tar.gzhttps://ftp.gn…

第五章 模型篇: 模型保存与加载

参考教程: https://pytorch.org/tutorials/beginner/basics/saveloadrun_tutorial.html 文章目录 pytorch中的保存与加载torch.save()torch.load()代码示例 模型的保存与加载保存 state_dict()nn.Module().load_state_dict()加载模型参数保存模型本身加载模型本身 c…

Scala--03

第6章 面向对象 Scala 的面向对象思想和Java 的面向对象思想和概念是一致的。 Scala 中语法和 Java 不同,补充了更多的功能。 6.1类和对象详解 6.1.1组成结构 构造函数: 在创建对象的时候给属性赋值 成员变量: 成员方法(函数) 局部变量 代码块 6.1.2构造器…

【深入浅出密码学】RSA

RSA密码体制 引言: RSA加密的本意并不是为了取代对称密码,而且它比诸如AES的密码要慢很多,因为RSA当中涉及许多数学计算,RSA通常和类似AES的对称密码一起使用,真正用来加密大量数据的是对称密码。而RSA主要保护对称密…

理解3ds max中的容器的概念

实验一: 在场景中创建一个容器 把这个容器保存为一个文件,在文件夹中可看到此容器文件,其大小为892KB,同时可看到生成一个同名的lock类型文件。 将场景中的某一个物体(面加多一点的)添加到容器中&#x…

Web安全测试中常见逻辑漏洞解析(实战篇)

前言: 越权漏洞是比较常见的漏洞类型,越权漏洞可以理解为,一个正常的用户A通常只能够对自己的一些信息进行增删改查,但是由于程序员的一时疏忽,对信息进行增删改查的时候没有进行一个判断,判断所需要操作的…

《Python之禅》让我们的python代码更加优美

之前在另外一篇文章中有总结过一部分关于python命名的规范,也帮助到了一些小伙伴,今天这篇文章主要是分享下《Python之禅》 历史文章跳转:python常用命名规范_python命名规范_花生君的博客-CSDN博客 查看《The Zen of Python》方法&#xf…