今天学习一个比较少用的备份模式-----备忘录模式。通过名字应该都可以知道其作用了,就是对象或对象属性作备份,方便随便取回。拿打怪来说,开始我有100血,打着打着,剩20了,我想重玩,重新回到100血的状态。用备忘录实现此情形的类图如下:
没错,一个简单的功能备忘录标准要用三个类实现。可能你会说为什么要个备忘录管理类,没必要啊,没有管理类功能照样可以实现,但是这样客户端需要关心备忘录,这对迪米特法则是一种亵渎。对高层模块来说,它最希望要做的就是创建一个备份点,然后在需要的时候再恢复到这个备份点就成了,它不用关心到底有没有备忘录这个类。因此才有了备忘录管理类的存在。下面看实现代码:
<?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、封装得更好一点
在系统管理中,一个备份的数据是完全、绝对不能修改的,它保证数据的洁净,避免数据污染而使备份失去意义。
备忘录模式是设计上的“月光宝盒”,可以让我们回到需要的年代,是程序数据的“后悔药”,吃了它就可以返回上一个状态,是设计人员的定义丸,确保即使在最坏的情况下也能获取最近的对象状态。因此,在设计的时候就不要使用数据库的临时表作为缓存备份数据了,虽然是一个简单的方法,但是它加大了数据库操作的频繁度,把压力下放到数据库了,最好的解决方法就是使用备忘录模式。