今天学习一个比较少用的备份模式-----备忘录模式。通过名字应该都可以知道其作用了,就是对象或对象属性作备份,方便随便取回。拿打怪来说,开始我有100血,打着打着,剩20了,我想重玩,重新回到100血的状态。用备忘录实现此情形的类图如下:
没错,一个简单的功能备忘录标准要用三个类实现。可能你会说为什么要个备忘录管理类,没必要啊,没有管理类功能照样可以实现,但是这样客户端需要关心备忘录,这对迪米特法则是一种亵渎。对高层模块来说,它最希望要做的就是创建一个备份点,然后在需要的时候再恢复到这个备份点就成了,它不用关心到底有没有备忘录这个类。因此才有了备忘录管理类的存在。下面看实现代码:
class="php"><?php class Gamer { private $blood = 0; public function getBlood() { return $this->blood; } public function setBlood( $blood ) { $this->blood = $blood; } public function createMemento() { return new Memento( $this->blood ); } public function restoreMemento( Memento $memento ) { $this->setBlood( $memento->getBlood() ); } } class Memento { private $blood = 0; public function __construct( $blood ) { $this->blood = $blood; } public function getBlood() { return $this->blood; } public function setBlood( $blood ) { $this->blood = $blood; } } class Caretasker { private $memento; public function getMemento() { return $this->memento; } public function setMemento( Memento $memento ) { $this->memento = $memento; } } $caretasker = new Caretasker(); $gamer = new Gamer(); $gamer->setBlood(100); echo "初始血量:100\n"; $caretasker->setMemento($gamer->createMemento()); $gamer->setBlood(20); echo "打怪后血量:20\n"; $gamer->restoreMemento($caretasker->getMemento()); echo "重玩恢复血量:".$gamer->getBlood(); ?> 运行结果: 初始血量:100 打怪后血量:20 重玩恢复血量:100[Finished in 0.3s]
?
?
备忘录模式的定义
备忘录模式提供了一种弥补真实世界缺陷的方法,让“后悔药”在程序的世界中真实可行。其定义为:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之个保存这个状态。这样以后就可将对象恢复到原先保存的状态。其主要由三个角色构成:
1、Originator发起人角色
记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
2、Memento备忘录角色
负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
3、Caretaker备忘录管理员角色
对备忘录进行管理、保存和提供备忘录
?
?
?
备忘录模式的应用
由于备忘录模式有太多的变形和处理方式,每种方式都有它自己的优点和缺点,标准和备忘模式很难在项目中遇到,基本上都有一些变换处理方式。因此,在使用备忘录模式时主要了解如何应用以需要注意哪些事项就成了
?
?
?
备忘录模式的使用场景
1、需要保存和恢复数据的相关状态场景
2、提供一个可回滚操作
3、需要监控的一个副本场景中。例如要监控一个对象的属性,但是监控又不应该作为系统的主业务来调用,它只是边缘应用,即使出现监控不准,错误报警也影响不大,因此一般的做法是备份一个主线程的对象,然后由分析程序来分析。
4、数据库连接的事务管理就是用的备忘录模式。
?
?
备忘录模式的注意事项
1、备忘录的生命期
备忘录创建出来就要在“最近”的代码中使用,要主动管理它的生命周期,建立就要使用,不使用就要立刻删除其引用,等待垃圾回收器对它的回收处理
2、备忘录的性能
不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中),原因有二:一是控制不了备忘录建立的对象数量,二是大对象的建立是要消耗资源的,系统的性能需要考虑。
?
?
?
备忘录模式的扩展
1、clone方式的备忘录
为保存一个状态写三个类太烦?还记得以前学过的原型模式吗?现在使用原型模式来实现:
<?php class Gamer { private $blood = 0; private $backup; public function getBlood() { return $this->blood; } public function setBlood( $blood ) { $this->blood = $blood; } public function createMemento() { $this->backup = clone $this; } public function restoreMemento() { $this->setBlood( $this->backup->getBlood() ); } } $gamer = new Gamer(); $gamer->setBlood(100); echo "初始血量:100\n"; $gamer->createMemento(); $gamer->setBlood(20); echo "打怪后血量:20\n"; $gamer->restoreMemento(); echo "重玩恢复血量:".$gamer->getBlood(); ?> 运行结果: 初始血量:100 打怪后血量:20 重玩恢复血量:100[Finished in 0.3s]
?天啊,太简单了是不。但这还是备忘录模式吗?它定义是“在该对象之外保存这个状态”,而现在却把这个状态保存在发起人内部。是的,设计模式定义的诞生过早,它没有想到之后的程序是这么有活力,有远见,而且在面对对象的设计中,即使把一个类封装在另一个类中也是可以做到的,何况一个小小的对象复制,这是它的设计模式完全没有预见到的。注意的是,使用clone方式的备忘录模式,可以使用在比较简单的场景或者比较单一场景中,尽量不要与其他的对象产生严重的耦合关系。
?
2、多状态的备忘录模式
这个就是备份多个属性,在之前的例子除了保存“血量”外,比如再保存个“气量”。
3、多备份的备忘录
4、封装得更好一点
在系统管理中,一个备份的数据是完全、绝对不能修改的,它保证数据的洁净,避免数据污染而使备份失去意义。
?
?
备忘录模式是设计上的“月光宝盒”,可以让我们回到需要的年代,是程序数据的“后悔药”,吃了它就可以返回上一个状态,是设计人员的定义丸,确保即使在最坏的情况下也能获取最近的对象状态。因此,在设计的时候就不要使用数据库的临时表作为缓存备份数据了,虽然是一个简单的方法,但是它加大了数据库操作的频繁度,把压力下放到数据库了,最好的解决方法就是使用备忘录模式。