设计模式 21 备忘录模式 Memento Pattern

news2024/9/22 13:45:45
设计模式 21 备忘录模式 Memento Pattern
1.定义

        备忘录模式是一种行为型设计模式,它允许你将一个对象的状态保存到一个独立的“备忘录”对象中,并在之后恢复到该状态。


2.内涵

主要用于以下场景:

需要保存对象状态以备恢复: 当你想要在进行一些可能导致对象状态改变的操作之前,先保存对象当前状态,以便在操作失败或需要撤销操作时能够恢复到之前的状态。
需要提供撤销/重做功能: 备忘录模式可以用来实现撤销和重做功能,通过保存一系列状态,用户可以回退到之前的操作步骤。
需要将对象状态序列化: 备忘录模式可以将对象状态序列化到一个独立的对象中,方便存储或传输。

主要模块如下:

  • 发起者(Originator): 需要保存状态的对象。
  • 备忘录(Memento): 保存发起者状态的对象。
  • 守护者(Caretaker): 负责管理备忘录对象,通常会保存多个备忘录,以便实现撤销/重做功能。

解释:

Originator 对象拥有一个 createMemento() 方法,用于创建 Memento 对象并保存自身状态。同时,它也拥有一个 setMemento() 方法,用于从 Memento 对象中恢复自身状态。
Memento 对象保存了 Originator 对象的状态,并且只允许 Originator 对象访问其内部状态。
Caretaker 对象负责管理 Memento 对象,它拥有 addMemento() 和 getMemento() 方法,分别用于添加和获取 Memento 对象。
类图关系:

Originator 对象与 Memento 对象之间存在关联关系,因为 Originator 对象创建并使用 Memento 对象。
Caretaker 对象与 Memento 对象之间也存在关联关系,因为 Caretaker 对象管理 Memento 对象。


3.案例分析

class Memento {
 public:
  virtual ~Memento() {}
  virtual std::string GetName() const = 0;
  virtual std::string date() const = 0;
  virtual std::string state() const = 0;
};

/**
 * The Concrete Memento contains the infrastructure for storing the Originator's
 * state.
 */
class ConcreteMemento : public Memento {
 private:
  std::string state_;
  std::string date_;

 public:
  ConcreteMemento(std::string state) : state_(state) {
    this->state_ = state;
    std::time_t now = std::time(0);
    this->date_ = std::ctime(&now);
  }
  /**
   * The Originator uses this method when restoring its state.
   */
  std::string state() const override {
    return this->state_;
  }
  /**
   * The rest of the methods are used by the Caretaker to display metadata.
   */
  std::string GetName() const override {
    return this->date_ + " / (" + this->state_.substr(0, 9) + "...)";
  }
  std::string date() const override {
    return this->date_;
  }
};

/**
 * The Originator holds some important state that may change over time. It also
 * defines a method for saving the state inside a memento and another method for
 * restoring the state from it.
 */
class Originator {
  /**
   * @var string For the sake of simplicity, the originator's state is stored
   * inside a single variable.
   */
 private:
  std::string state_;

  std::string GenerateRandomString(int length = 10) {
    const char alphanum[] =
        "0123456789"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz";
    int stringLength = sizeof(alphanum) - 1;

    std::string random_string;
    for (int i = 0; i < length; i++) {
      random_string += alphanum[std::rand() % stringLength];
    }
    return random_string;
  }

 public:
  Originator(std::string state) : state_(state) {
    std::cout << "Originator: My initial state is: " << this->state_ << "\n";
  }
  /**
   * The Originator's business logic may affect its internal state. Therefore,
   * the client should backup the state before launching methods of the business
   * logic via the save() method.
   */
  void DoSomething() {
    std::cout << "Originator: I'm doing something important.\n";
    this->state_ = this->GenerateRandomString(30);
    std::cout << "Originator: and my state has changed to: " << this->state_ << "\n";
  }

  /**
   * Saves the current state inside a memento.
   */
  Memento *Save() {
    return new ConcreteMemento(this->state_);
  }
  /**
   * Restores the Originator's state from a memento object.
   */
  void Restore(Memento *memento) {
    this->state_ = memento->state();
    std::cout << "Originator: My state has changed to: " << this->state_ << "\n";
  }
};

/**
 * The Caretaker doesn't depend on the Concrete Memento class. Therefore, it
 * doesn't have access to the originator's state, stored inside the memento. It
 * works with all mementos via the base Memento interface.
 */
class Caretaker {
  /**
   * @var Memento[]
   */
 private:
  std::vector<Memento *> mementos_;

  /**
   * @var Originator
   */
  Originator *originator_;

 public:
     Caretaker(Originator* originator) : originator_(originator) {
     }

     ~Caretaker() {
         for (auto m : mementos_) delete m;
     }

  void Backup() {
    std::cout << "\nCaretaker: Saving Originator's state...\n";
    this->mementos_.push_back(this->originator_->Save());
  }
  void Undo() {
    if (!this->mementos_.size()) {
      return;
    }
    Memento *memento = this->mementos_.back();
    this->mementos_.pop_back();
    std::cout << "Caretaker: Restoring state to: " << memento->GetName() << "\n";
    try {
      this->originator_->Restore(memento);
    } catch (...) {
      this->Undo();
    }
  }
  void ShowHistory() const {
    std::cout << "Caretaker: Here's the list of mementos:\n";
    for (Memento *memento : this->mementos_) {
      std::cout << memento->GetName() << "\n";
    }
  }
};



/**
 * Client code.
 */

void ClientCode() {
  Originator *originator = new Originator("Super-duper-super-puper-super.");
  Caretaker *caretaker = new Caretaker(originator);
  caretaker->Backup();
  originator->DoSomething();
  caretaker->Backup();
  originator->DoSomething();
  caretaker->Backup();
  originator->DoSomething();
  std::cout << "\n";
  caretaker->ShowHistory();
  std::cout << "\nClient: Now, let's rollback!\n\n";
  caretaker->Undo();
  std::cout << "\nClient: Once more!\n\n";
  caretaker->Undo();

  delete originator;
  delete caretaker;
}

int main() {
  std::srand(static_cast<unsigned int>(std::time(NULL)));
  ClientCode();
  return 0;
}

4.注意事项

备忘录模式在使用时需要注意以下几点:

1. 备忘录的封装性:

备忘录对象应该封装 Originator 的内部状态,并对外部隐藏这些状态。这意味着 Caretaker 只能通过 Originator 提供的接口来访问备忘录对象,而不能直接访问备忘录对象内部的状态。
这可以防止 Caretaker 意外修改 Originator 的状态,并确保状态的一致性。


2. 备忘录的管理:

Caretaker 应该负责管理备忘录对象,例如存储、检索和删除备忘录。
Caretaker 应该提供方法来添加、获取和删除备忘录,以便 Originator 可以方便地保存和恢复其状态。


3. 备忘录的性能:

创建和存储备忘录对象可能会消耗一定的资源,因此需要考虑性能问题。
可以通过使用轻量级的备忘录对象或使用缓存机制来提高性能。


4. 备忘录的安全性:

备忘录对象可能包含敏感信息,因此需要考虑安全性问题。
可以使用加密或访问控制机制来保护备忘录对象。


5. 备忘录的使用场景:

备忘录模式适用于需要保存和恢复对象状态的场景,例如撤销/重做功能、游戏存档和恢复等。
需要根据具体场景选择合适的备忘录实现方式,例如使用简单的数据结构或使用更复杂的序列化机制。

5.最佳实践

在使用备忘录模式时,以下是一些值得遵循的经验:

1. 保持备忘录的简单性:

备忘录对象应该只包含 Originator 状态的必要信息,避免过度复杂化。
尽量使用简单的数据结构来存储状态,例如字典、列表等,避免使用复杂的自定义对象。
2. 使用工厂模式创建备忘录:

使用工厂模式可以简化备忘录对象的创建过程,并隐藏备忘录对象的具体实现细节。
工厂模式可以根据不同的场景创建不同的备忘录对象,例如创建不同的备忘录类型来保存不同的状态信息。
3. 使用策略模式管理备忘录:

使用策略模式可以根据不同的需求选择不同的备忘录管理策略,例如使用不同的存储方式或不同的访问控制机制。
策略模式可以提高代码的可扩展性和灵活性,方便根据需求进行修改和扩展。
4. 使用缓存机制提高性能:

可以使用缓存机制来存储最近使用的备忘录对象,避免重复创建备忘录对象。
缓存机制可以提高性能,尤其是在频繁保存和恢复状态的场景下。
5. 使用序列化机制保存备忘录:

可以使用序列化机制将备忘录对象保存到文件或数据库中,以便持久化保存状态。
序列化机制可以方便地保存和恢复状态,并支持跨平台使用。
6. 考虑安全性问题:

如果备忘录对象包含敏感信息,需要考虑安全性问题,例如使用加密或访问控制机制来保护备忘录对象。
可以使用安全策略来限制对备忘录对象的访问权限,并确保信息的安全性。

6.总结

备忘录模式是一种强大的设计模式,可以帮助我们实现状态的保存和恢复功能。在使用备忘录模式时,需要注意封装性、管理、性能、安全性以及使用场景等方面,以确保其有效性和安全性。

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

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

相关文章

Kubernetes集群调度

一.List-Watch 1.调度约束 Kubernetes 是通过 List-Watch **** 的机制进行每个组件的协作&#xff0c;保持数据同步的&#xff0c;每个组件之间的设计实现了解耦。 用户是通过 kubectl 根据配置文件&#xff0c;向 APIServer 发送命令&#xff0c;在 Node 节点上面建立 P…

【OCPP】ocpp1.6协议第3.13章节SmartCharging介绍及翻译

目录 3.13 SmartCharging智能充电-概述 智能充电的目标 关键功能 消息类型 负载管理 动态电量配置 总结 3.13 SmartCharging智能充电-译文 3.13.1 Charging Profile Purpose充电配置的目的 3.13.2 Stacking charging profile堆叠充电配置 3.13.3 Combining charging profile pu…

如何在线转换图片的格式?一键修改图片格式的方法

图片是日常生活和工作中的一种常用的内容展示类型&#xff0c;在使用图片的时候不同用途需要使用的图片格式也是不同的&#xff0c;比如我们手中有一张jpg格式图片&#xff0c;但是平台上传要求格式是png&#xff0c;那么怎样才能将jpg转png格式呢&#xff1f;下面将教大家图片…

前端面试:项目细节重难点问题分享

面试官提问&#xff1a;我现在给你出一个项目实际遇到的问题&#xff1a;由于后端比较忙&#xff0c;所以我们这边的列表数据排序需要前端最近实现&#xff0c;那你会怎么实现排序呢&#xff1f; 答&#xff1a;我的回答&#xff1a;确实&#xff0c;数据都是由后端实现的&…

2024年上半年软件设计师试题及答案(回忆版)--选择题

基础知识选择题 基础知识选择题 1,2,3][4,5,6][1,2,3,4,5,6] &#xff08;总&#xff1a;1分&#xff09; &#xff08;注意&#xff1a;括号内的是截止当前题目总分&#xff09; vlan不能隔绝内外网 &#xff08;2分&#xff09; 链路层使用交换机&#xff0c;…

关于已配好java环境但双击无法打开jar包的解决方案

如果你已经装好了 java 环境直接跳到最后看解决方法即可 先说一下你安装的 java 环境&#xff0c;如果完全是默认选项安装&#xff0c;则会安装 jdk 和 jre&#xff0c;并且在安装 jre 时还需要安装目录下为空&#xff0c;其实 jre 的安装是多余的&#xff0c;因为安装的 jdk 里…

Integer包装类

面试题&#xff1a; 自动装箱:把基本数据类型会自动的变成其对应的包装类 自动拆箱:把包装类自动的变成其对象的基本数据类型 package Integer;public class Demo {public static void main(String[] args) {Integer i 10;String binaryString Integer.toBinaryString(i);Str…

pyqt QLineEdit单行文本框控件

pyqt QLineEdit单行文本框控件 介绍效果代码 介绍 QLineEdit 是 PyQt中的一个控件&#xff0c;用于输入和编辑单行文本。它允许用户输入和编辑一行纯文本&#xff0c;并提供了许多有用的功能&#xff0c;如文本验证、密码输入&#xff08;显示为点或星号&#xff09;、自动完成…

ICode国际青少年编程竞赛- Python-6级训练场-递归入门

ICode国际青少年编程竞赛- Python-6级训练场-递归入门 1、 def recur(n):# 边界条件if n<1:return# 额外动作Dev.step(n)Dev.turnRight()# 递归调用recur(n-1)recur(8)2、 def recur(n):# 边界条件if n<1:return# 额外动作Dev.step(n)Dev.turnLeft()# 递归调用recur(n-1)…

一些Spring的理解

说说你对Spring的理解 首先Spring是一个生态&#xff1a;可以构建企业级应用程序所需的一切基础设施 但是&#xff0c;通常Spring指的就是Spring Framework&#xff0c;它有两大核心&#xff1a; IOC和DI 它的核心就是一个对象管理工厂容器&#xff0c;Spring工厂用于生产Bea…

初识STM32单片机-TIM定时器

初识STM32单片机-TIM定时器 一、定时器概述二、定时器类型2.1 基本定时器(TIM6和TIM7)2.2 通用定时器(TIM2、TIM3、TIM4和TIM5)2.3 高级定时器(TIM1和TIM8) 三、定时中断基本结构和时基单元工作时序3.1 定时器基本结构3.2 预分频器时序3.3 计数器时序3.3.1 计数器有无预装时序(…

七大获取免费https的方式

想要实现https访问最简单有效的的方法就是安装SSL证书。只要证书正常安装上以后&#xff0c;浏览器就不会出现网站不安全提示或者访问被拦截的情况。下面我来教大家怎么去获取免费的SSL证书&#xff0c;又如何安装证书实现https访问。 一、选择免费SSL证书提供商 有多家机构提…

国产PS插件新选择;StartAI平替中的佼佼者!

前言 在设计的世界里&#xff0c;每一个细节都至关重要。设计师们常常面临时间紧迫、创意受限、工具复杂等挑战。Photoshop虽强大&#xff0c;但繁琐的操作和高昂的成本往往令人望而却步。今天我就为大家介绍一款PSAI插件——StartAI&#xff0c;一款专为Photoshop设计的国产A…

手绘任意波

上位机发送手绘波形数据&#xff0c;下位机接收并输出。 支持 STM32 STC arduino Pico 等多种单片机&#xff0c;内置或外置 DAC 实现。 ​​​​​​​ 篇幅所限&#xff0c;更多内容请访问我的网站&#xff1a; jiangge12.github.io 十二江哥的网站 (jiangge12.github.io)…

什么是期权内在价值?怎么计算?

今天期权懂带你了解什么是期权内在价值&#xff1f;怎么计算&#xff1f;内在价值&#xff0c;也称为内涵价值、内在价格&#xff0c;指的是若期权合约到期&#xff0c;权利方能够赚到的市场价与行权价之间的差价收益。 什么是期权内在价值&#xff1f; 期权的内在价值是指期权…

window安装ffmpeg播放本地摄像头视频

1、安装ffmpeg ffmpeg官方网站&#xff1a;FFmpeg 下载后解压文件夹名为ffmpeg 2、设置环境变量 目录 1、安装ffmpeg 设置环境变量 以F:\software\after\ffmpeg\bin为例 在命令行中输入ffmpeg出现下方代表安装成功 3、通过ffmpeg播放本地电脑摄像头 鼠标右击开始按钮&…

R语言学习 - 箱线图一步法

箱线图 - 一步绘制 绘图时通常会碰到两个头疼的问题&#xff1a; 1、有时需要绘制很多的图&#xff0c;唯一的不同就是输入文件&#xff0c;其它都不需要修改。如果用R脚本&#xff0c;需要反复替换文件名&#xff0c;繁琐又容易出错。 (R也有命令行参数&#xff0c;不熟&…

3D开发工具HOOPS在BIM系统中的应用

建筑信息模型是一种革命性的建筑设计、施工和管理方法。它通过创建和利用数字信息来优化建筑项目的设计、施工和运营过程。在这个过程中&#xff0c;3D开发工具HOOPS扮演着至关重要的角色&#xff0c;为BIM系统提供了强大的技术支持和丰富的功能。HOOPS中文网http://techsoft3d…

IC开发——verdi基本用法

1. 基础知识 1.1. verdi VCS和Verdi这两个工具&#xff0c;这两个工具目前都属于synopsys公司。VCS主要负责编译运行Testbench和RTL&#xff0c;并负责生成相应的波形文件。而verdi主要负责加载波形文件&#xff0c;查看信号的波形及其对应的代码来进行调试验证。Verdi最开始…

Vulnhub靶机 whowantsobeking :1 打靶 渗透详细过程(萌新)

Vulnhub靶机搭建配置 先搭建vulnhub靶机&#xff1a;https://www.vulnhub.com/entry/who-wants-to-be-king-1,610/ 下载镜像之后whowantsobeking.ova后&#xff0c;用VMware Workstation Pro打开依次点击文件-打开&#xff0c;选择我们刚才下载的ova文件打开&#xff0c;修改…