【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);
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
备忘录模式是一种行为型设计模式,它允许在不破坏封装性的前提下捕获对象的内部状态,并在对象之外保存这个状态,以便在以后的某个时候将其恢复到原先的状态备忘录模式由三个主要角色组成: 1. 发起人(Originator):负责创建一个备忘录,并记录和恢复自身的内部状态。 2. 备忘录Memento):负责存储发起人对象的内部状态。 3. 管理者(Caretaker):负责保存备忘录,并可以对其进行管理,但不知道备忘录的具体内容。 该模式的工作流程如下: 1. 发起人创建一个备忘录,将自己的状态保存在备忘录中。 2. 发起人可以根据需要在任何时候保存自己的状态。 3. 管理者将备忘录保存起来,可维护多个备忘录。 4. 在需要恢复状态的时候,可以通过管理者获取相应的备忘录,并将发起人的状态恢复到备忘录中保存的状态备忘录模式的优点是: - 发起人对象与备忘录对象分离,实现了信息的封装和隐藏。 - 备忘录对象存储了发起人对象的历史状态,可以实现撤销和恢复操作。 - 备忘录可以进行存储和管理,可进行序列化和持久化操作备忘录模式通常适用于以下情况: - 需要保存和恢复对象的状态,并且封装对象的状态对于其他对象来说是不可见的。 - 需要实现撤销和恢复功能。 - 需要保存和恢复部分或全部对象状态的场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ac君

在你们的鼓励下我会多多分享代码

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值