• 【Memento模式】C++设计模式——备忘录模式



        C++设计模式大全,23种设计模式合集详解—👉(点我跳转)

    一、设计流程探讨

      假如你正在开发一款文字编辑器应用程序。除了简单的文字编辑功能外,编辑器中还要有设置文本格式和插入内嵌图片等功能。
      后来,你决定让用户能撤销施加在文本上的任何操作。这项功能在过去几年里变得十分普遍,因此用户期待任何程序都有这项功能。你选择采用直接的方式来实现该功能:程序在执行任何操作前会记录所有的对象状态,并将其保存下来。当用户此后需要撤销某个操作时,程序将从历史记录中获取最近的快照,然后使用它来恢复所有对象的状态。
    在这里插入图片描述
      让我们来思考一下这些状态快照。首先,到底该如何生成一个快照呢?很可能你会需要遍历对象的所有成员变量并将其数值复制保存。但只有当对象对其内容没有严格访问权限限制的情况下,你才能使用该方式。不过很遗憾,绝大部分对象会使用私有成员变量来存储重要数据,这样别人就无法轻易查看其中的内容。
      现在我们暂时忽略这个问题,假设对象都像嬉皮士一样:喜欢开放式的关系并会公开其所有状态。尽管这种方式能够解决当前问题,让你可随时生成对象的状态快照,但这种方式仍存在一些严重问题。未来你可能会添加或删除一些成员变量。这听上去很简单,但需要对负责复制受影响对象状态的类进行更改。
    在这里插入图片描述
      还有更多问题。让我们来考虑编辑器(Editor)状态的实际 “快照”,它需要包含哪些数据?至少必须包含实际的文本、 光标坐标和当前滚动条位置等。你需要收集这些数据并将其放入特定容器中,才能生成快照。
      你很可能会将大量的容器对象存储在历史记录列表中。这样一来,容器最终大概率会成为同一个类的对象。这个类中几乎没有任何方法,但有许多与编辑器状态一一对应的成员变量。为了让其他对象能保存或读取快照,你很可能需要将快照的成员变量设为公有。无论这些状态是否私有,其都将暴露一切编辑器状态。其他类会对快照类的每个小改动产生依赖,除非这些改动仅存在于私有成员变量或方法中,而不会影响外部类。
      我们似乎走进了一条死胡同:要么会暴露类的所有内部细节而使其过于脆弱;要么会限制对其状态的访问权限而无法生成快照。那么,我们还有其他方式来实现 “撤销” 功能吗?
    解决方案:
      我们刚才遇到的所有问题都是封装 “破损” 造成的。一些对象试图超出其职责范围的工作。由于在执行某些行为时需要获取数据,所以它们侵入了其他对象的私有空间,而不是让这些对象来完成实际的工作。
      备忘录模式将创建状态快照(Snapshot)的工作委派给实际状态的拥有者原发器(Originator)对象。这样其他对象就不再需要从 “外部” 复制编辑器状态了,编辑器类拥有其状态的完全访问权,因此可以自行生成快照。
      模式建议将对象状态的副本存储在一个名为备忘录(Memento)的特殊对象中。除了创建备忘录的对象外,任何对象都不能访问备忘录的内容。其他对象必须使用受限接口与备忘录进行交互,它们可以获取快照的元数据(创建时间和操作名称等),但不能获取快照中原始对象的状态。
    在这里插入图片描述
      这种限制策略允许你将备忘录保存在通常被称为负责人(Caretakers)的对象中。由于负责人仅通过受限接口与备忘录互动,故其无法修改存储在备忘录内部的状态。同时,原发器拥有对备忘录所有成员的访问权限,从而能随时恢复其以前的状态。
      在文字编辑器的示例中,我们可以创建一个独立的历史(History)类作为负责人。编辑器每次执行操作前,存储在负责人中的备忘录栈都会生长。你甚至可以在应用的 UI 中渲染该栈,为用户显示之前的操作历史。
      当用户触发撤销操作时,历史类将从栈中取回最近的备忘录,并将其传递给编辑器以请求进行回滚。由于编辑器拥有对备忘录的完全访问权限,因此它可以使用从备忘录中获取的数值来替换自身的状态。

    二、模式介绍

      虽然这个模式在我们当前的时代有些过时了,但我们也需要了解下该模式。当今实现的 Memento 模式很大部分都不是以前的那种面向对象的设计方式了(如字段映射的方法过于低级),但是 “实现对信息隐藏” 的思想没有变,从而实现对信息状态的保存。
    (1)模式动机
      在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。
      如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。
    (2)模式定义
      在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
    (3)要点总结
    a). 备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。
    b). Memento模式的核心是信息隐藏,即 Originator 需要向外接隐藏信息,保持其封装性。但同时又需要将状态保持到外界(Memento) 。
    c). 由于现代语言运行时(如C#、Java等)都具有相当的对象序列化支持,因此往往采用效率较高、又较容易正确实现的序列化方案来实现Memento模式。

    三、代码实现

      备忘录有三种比较常见的实现方式,在这里先举例出它们的类结构图,不对它们的代码逐一剖析。
    基于嵌套类的实现
    在这里插入图片描述

    封装更加严格的实现
    在这里插入图片描述


      以下是备忘录模式经典例子的类图结构,以及伪代码的实现。但是缺点之一就像模式介绍所说的,这需要映射各种字段,这太低级了。
    在这里插入图片描述

    class Memento{
    	string state;		 //映射字段
    	//...还有很多状态,相当于其他需映射的字段
    public:
    	Memento(const string &s):state(s){}
    	string getState() const { return state; }
    	void setState(const string &s) { state = s; }
    };
    class Originator{		//这个类我们希望继续保持信息隐藏的特性
    	string state;		//映射字段
    	//...还有很多状态,相当于其他需映射的字段
    public:
    	Originator(){}
    	Memento createMemento(){
    		Memento m(state);
    		return m;
    	}
    	void setMemento(const Memento &m) { state = m.getState(); }
    };
    int main(){
    	Originator originator;
    	//存储到备忘录
    	Memento mem = originator.createMemento();
    	
    	//... 改变originator的状态
    
    	//从备忘录中恢复
    	originator.setMemento(mem);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    【云原生 | Kubernetes 实战】06、Pod高级实战:基于污点、容忍度、亲和性的多种调度策略(下)
    python爬虫-30-python之图形验证码技术
    kubernetes-pod的驱逐策略
    STM32 float浮点数转换成四个字节
    Python 的内置方法
    GitLab数据迁移后出现500错误
    基于微信小程序的青少年素质教育培训系统设计与实现-计算机毕业设计源码+LW文档
    多图预警~ 华为 ECS 与 阿里云 ECS 对比实战
    【前端笔记】ant-design-vue 3.x使用modal.method()自定义content内容小记
    网站速度测试和优化方案
  • 原文地址:https://blog.csdn.net/u012011079/article/details/126173922