读书笔记-设计模式-可复用版-Memento 备忘录模式

发表于2019-07-01
评论0 3.3k浏览

读书笔记-设计模式-可复用版-Memento 备忘录模式


 

https://en.wikipedia.org/wiki/Memento_pattern


 

以前觉得Memento备忘录模式,不算太常用的一种,但仔细想想,尤其是在游戏中,存储游戏的进度就是应用的一种,但通常游戏的进度我们是以序列化字节流的形式存储在本地的持久化目录中,另一种是存储在内存中,比如说一些三消游戏,2048,都会有回退的功能设计,那么我先前的每一步cao作,我都要进行记录,这里也是典型的备忘录模式,和上一种区别只是他要存放在内存中,在运行中使用,而另一个在磁盘上。


 

简单总结:

存储的位置有两种:

1.运行时(内存)

2.本地(持久化存储)


 

应用案例:游戏进度存档,撤销,回退,悔棋等cao作。如果支持多个存储,就相当于check points,你可以选择回到哪一个进度,尤其是很早以前玩的PC端的RPG游戏,都会有多个存档,通常这种游戏都比较复杂庞大,提供多个存储供玩家选择。相当于后悔药。


 

意图:


 

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象的外部保存这个状态,这样以后在需要的时候,可以恢复到以前的状态。


 

内部状态(internal state):你想要保存的数据,或是你未来想要进行恢复的数据。


 

通常这些数据不是整个对象,只是个别的重要参数,比如2048中,我移动几次以后,我要回退cao作,需要记录的是上一次和新的合成后的一些数据,比如旧的坐标和类型,以及移动后新的坐标和类型等等。


 

在不破坏封装性的前提下,说明内部状态的数据,不在当前的对象中存储,而是另外一个类中,这个类叫Memento,大多数的资料翻译成为“备忘录”,但是你在搜索引擎中查找的时候,会发现有一部电影蹦了出来:


 

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1


 

Memento 译成记忆碎片,我认为最为贴切的,这部电影一直想看,但都没有找到合适的机会,据说很烧脑,这次真的很巧合,这周末就挤出来时间看看!我希望是明天或是后天,一边看,一边吃盒马鲜生哈哈哈哈:)


 

记忆碎片,记录了过去某一段时间发生的事儿,你可以选择回到某一段记忆中,如果人生也可以这样。。。。


 

三个关键的术语:


 

Memento

Originator

Caretaker


 

第一个是记忆碎片,很好理解,第二个和第三方,刚开始觉得怎么命名这么奇怪,

Originator译为原发器,实际上,他就是一个包含上面提到过的内部状态的对象,

可以是任意的对象,换到现实生活中,可以是任何事物,都有自己的内部状态,

都在发生着变化。


 

Memento 里会有这些重要的参数,保存着这些参数。


 

Caretaker相当于一个MementoManager,记忆碎片的管理类,特点是,只能存储Memento,但不能进行修改,这也是为了封装性,保护数据不被破坏,但在这里也是必然的,记忆碎片中的数据绝对不可以进行修改的,不然就是寻秦记啦。


 

这就是三个独立的类,三者之间的关系如下:


 

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1


 

Originator负责创建和恢复Memento,因为封装性,外部是访问不到这些数据的。所以创建也必须亲力亲为。


 

Memento依赖Originator,毕竟是要保存Originator的internal State,Caretaker用于保存Memento,它的管理器,但例子中通常是只保存一个Memento,这样每次新的保存都会覆盖掉旧的,如果支持多个记忆碎片的保存,那么就得通过数组,或是字典的形式。


 

Caretaker管理着Memeno,只可以进行保存和用于获取,但不可以进行修改,上面也解释到。


 

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1


 


 

时序图,我在保存的时候,由Originator调用createMemento,创建记忆碎片,Caretaker来保存它,直接指向这个引用即可,当需要恢复的时候,也是通过Caretaker获取,但他并不关心Caretaker,只要能给我Memento数据就好,Originator和Memento可以是单向也可以是双向的耦合关系(取决于实现方式等),Caretaker和Memento则是单向耦合关系。 


 

举两个栗子,一个是wikipedia上的,一个是2048游戏中回退功能的说明,


 

无论是2048还是三消一类的游戏,如果我们要支持回退的cao,那么在每一个cao作的开始和结速都要保存每一个数字的状态,比如他的起始位置和类型(值),以后移动后的结束位置和(类型,可能会合成新的,也可能只是进行了移动),那么我们通常回退是支持多步的,

所以在Caretaker中可以使用Stack来保存Memento,这样回退cao作时,Pop即可。


 

下面是一个简要的代码实现,演示一下Memento设计模式的应用:

可以发一山代码上:


 

Memento里保存的内部状态参数,是readonly类型,只能初始化的时候赋值,不可以再进行修改,因为这些数据是禁止修改的。


 

另外,关于回退cao作,可以通过Command命令实现,这在后面会介绍到。


 

最后说下Memento的缺点,有的书上说当内部状态的数据量比较大的时候,那么存储耗时以及内存占用也都会上升,这是客观事实,这不能说是设计模式本身的缺点。


 

反倒我觉得麻烦的地方在于,如果内部状态的数据量比较大,那么Memento的设计就很重要,比如我总是频繁的增加变更内部状态,那么Memeno也要进行相应的cao作,这会比较繁锁,都是值类型还好,浅拷贝就搞定了,但如果有引用类型,就要涉及到深拷贝等等(原型Prototype设计模式),但可以使用序列化的形式来简化这种复杂的cao作,但也可能会有内存,效率,速度的问题等等,这才是在实际开发的过程中,要面临的问题。


 

现在序列化的应用无处不在,比如持久化的数据存储,主要是一劳永逸:))))


 

        2019.6.29 1:04 蓝色港湾 西西弗书店 Mitty

  • 允许他人重新传播作品,但他人重新传播时必须在所使用作品的正文开头的显著位置,注明用户的姓名、来源及其采用的知识共享协议,并与该作品在磨坊上的原发地址建立链接
  • 可对作品重新编排、修改、节选或者以作品为基础进行创作和发布
  • 可将作品进行商业性使用

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引