备忘录(Memento)模式,也叫快照(Snapshot)模式,英文翻译是 Memento Design Pattern。在 GoF 的《设计模式》一书中,备忘录模式是这么定义的:
Captures and externalizes an object’s internal state so that it can be restored later, all without violating encapsulation.
翻译成中文就是:在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。
我们通过一个例子来了解备忘录模式,相信大家应该都玩过一些一些含有存档的游戏吧,例如红警,魂斗罗等,每打完一个关卡后我们都会进行存档,防止下一关没有通过导致我们要从头开始。假设我们现在正在游戏的某个场景,游戏角色有生命力、攻击力、防御力等等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗前。这个代码应该如何实现呢,最简单的我们可以新建一个类用来储存角色的生命力,攻击力,防御力等数据。代码如下:
<?php
namespace DesignPatterns\Memento\v1;
class GameRole
{
private $vit;//生命力
private $atk;//攻击力
private $def;//防御力
/**
* @return mixed
*/
public function getVit()
{
return $this->vit;
}
/**
* @param mixed $vit
*/
public function setVit($vit): void
{
$this->vit = $vit;
}
/**
* @return mixed
*/
public function getAtk()
{
return $this->atk;
}
/**
* @param mixed $atk
*/
public function setAtk($atk): void
{
$this->atk = $atk;
}
/**
* @return mixed
*/
public function getDef()
{
return $this->def;
}
/**
* @param mixed $def
*/
public function setDef($def): void
{
$this->def = $def;
}
//角色状态显示
public function StateDisplay()
{
echo "角色当前的状态:<br>体力:".$this->vit,
"<br>攻击力: ".$this->atk,"<br>防御力: ".$this->def."<br>";
}
//获得初始状态
public function GetInitState(){
$this->vit = 100;
$this->atk = 100;
$this->def = 100;
}
//战斗
public function Fight(){
$this->vit = 0;
$this->atk = 0;
$this->def = 0;
}
}
运行Client.php
<?php
namespace DesignPatterns\Memento\v1;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
public function run()
{
//大战boss前 获得初始角色状态
$gameRole = new GameRole();
$gameRole->GetInitState();
$gameRole->StateDisplay();
//保存进度 通过游戏角色的新实例,来保存进度
$backup = new GameRole();
$backup->setVit($gameRole->getVit());
$backup->setAtk($gameRole->getAtk());
$backup->setDef($gameRole->getDef());
//大战boss损耗严重 所有数据损耗为0
$gameRole->Fight();
$gameRole->StateDisplay();
//恢复之前状态
$gameRole->setVit($backup->getVit());
$gameRole->setAtk($backup->getAtk());
$gameRole->setDef($backup->getDef());
$gameRole->StateDisplay();
}
}
$worker = new Client();
$worker->run();
运行结果:
这样的写法,确实是实现了我们的要求,但是问题也很多。主要的问题在于这客户端的调用。下面这一段有问题,因为这样写就把整个游戏角色的细节暴露给了客户端,你的客户端的职责就太大了,需要知道游戏角色的生命力、攻击力、防御力这些细节,还要对它进行‘备份’。以后需要增加新的数据,例如增加‘ 魔法力’或修改现有的某种力,例如‘生命力’改为‘经验值’, 这部分就一定要修改了。同样的道理也存在于恢复时的代码。
$backup = new GameRole();
$backup->setVit($gameRole->getVit());
$backup->setAtk($gameRole->getAtk());
$backup->setDef($gameRole->getDef());
$gameRole->setVit($backup->getVit());
$gameRole->setAtk($backup->getAtk());
$gameRole->setDef($backup->getDef());
显然,我们希望的是把这些‘游戏角色’的存取状态细节封装起来,而且最好是封装在外部的类当中。以体现职责分离。
这个就需要用到我们今天所要讲的备忘录模式了,我们一起来看看备忘录模式的结构与代码实现。
备忘录模式的结构与实现
结构
- Originator (发起人):负责创建一个备忘录Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。Originator 可根据需要决定Memento存储Originator的哪些内部状态。
- Memento (备忘录):负责存储Originator对象的内部状态,并可防止Originator以外的其他对象访问备忘录Memento。备忘录有两个接口,Caretaker 只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator 能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。
- Caretaker(管理者):负责保存好备忘录Memento,不能对备忘录的内容进行操作或检查。
就刚才的例子,‘ 游戏角色’类其实就是一个Originator,而我们用了同样的‘游戏角色’实例‘备份’来做备忘录,这在当需要保存全部信息时,是可以考虑的,而用clone的方式来实现Memento的状态保存可能是更好的办法,但是如果是这样的话,使得我们相当于对上层应用开放了Originator的全部( public)接口,这对于保存备份有时候是不合适的。
那如果我们不需要保存全部的信息以备使用时,怎么办? 这或许是很可能发生的情况,我们需要保存的如果并不是全部信息,而只是部分,那么就应该有一个独立的备忘录类Memento,它只拥有需要保存的信息的属性。
代码实现
Originator.php
<?php
namespace DesignPatterns\Memento\example;
/**
* 发起人类
* Class Originator
* @package DesignPatterns\Memento\example
*/
class Originator
{
private $state;
/**
* @param mixed $state
*/
public function setState($state): void
{
$this->state = $state;
}
/**
* @return mixed
*/
public function getState()
{
return $this->state;
}
//创建备忘录,将当前需要保存的信息导入并实例化出一个Memento对象
public function CreateMemento()
{
return new Memento($this->state);
}
//恢复备忘录 将Memento导入并恢复相关数据
public function setMemento(Memento $memento)
{
$this->state = $memento->getState();
}
//显示数据
public function show()
{
echo 'State:'.$this->state."<br>";
}
}
Memento.php
<?php
namespace DesignPatterns\Memento\example;
/**
* 备忘录类
* Class Memento
* @package DesignPatterns\Memento\example
*/
class Memento
{
private $state;
//构造方法 将相关数据导入
public function __construct($state)
{
$this->state = $state;
}
//需要保存的数据可能是多个
/**
* @param mixed $state
*/
public function setState($state): void
{
$this->state = $state;
}
/**
* @return mixed
*/
public function getState()
{
return $this->state;
}
}
Caretaker.php
<?php
namespace DesignPatterns\Memento\example;
/**
* 管理者类
* Class Caretaker
* @package DesignPatterns\Memento\example
*/
class Caretaker
{
private $memento;
//获取或者设置备忘录
/**
* @return mixed
*/
public function getMemento()
{
return $this->memento;
}
/**
* @param mixed $memento
*/
public function setMemento($memento): void
{
$this->memento = $memento;
}
}
Client.php
<?php
namespace DesignPatterns\Memento\example;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
public function run()
{
$originator = new Originator();
$originator->setState('on');//Originator 初始状态为on
$originator->show();
//保存状态时,由于有了很好的封装,可以隐藏Originator的实现细节
$caretaker = new Caretaker();
$caretaker->setMemento($originator->CreateMemento());
//更改状态属性为off
$originator->setState('off');
$originator->show();
$originator->setMemento($caretaker->getMemento());
$originator->show();
}
}
$worker = new Client();
$worker->run();
运行结果
代码中把要保存的细节给封装在了Memento中了,如果以后哪天要更改保存的细节也不会影响客户端了。
Memento模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator 可以根据保存的Memento信息还原到前一状态。使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来。
下面我们来使用备忘录模式把我们刚才的代码更改一下,代码如下:
GameRole.php
<?php
namespace DesignPatterns\Memento\v2;
/**
* 游戏角色类
* Class GameRole
* @package DesignPatterns\Memento\v2
*/
class GameRole
{
private $vit;//生命力
private $atk;//攻击力
private $def;//防御力
/**
* @return mixed
*/
public function getVit()
{
return $this->vit;
}
/**
* @param mixed $vit
*/
public function setVit($vit): void
{
$this->vit = $vit;
}
/**
* @return mixed
*/
public function getAtk()
{
return $this->atk;
}
/**
* @param mixed $atk
*/
public function setAtk($atk): void
{
$this->atk = $atk;
}
/**
* @return mixed
*/
public function getDef()
{
return $this->def;
}
/**
* @param mixed $def
*/
public function setDef($def): void
{
$this->def = $def;
}
//获得初始状态
public function GetInitState(){
$this->vit = 100;
$this->atk = 100;
$this->def = 100;
}
//战斗
public function Fight(){
$this->vit = 0;
$this->atk = 0;
$this->def = 0;
}
//角色状态显示
public function StateDisplay()
{
echo "角色当前的状态:<br>体力:".$this->vit,
"<br>攻击力:".$this->atk,"<br>防御力:".$this->def."<br>";
}
//新增“保存角色状态”方法,将游戏角色的三个状态值通过实例化“角色状态存储箱”返回
//保存角色状态
public function SaveRoleState()
{
return new RoleStateMemento($this->vit,$this->atk,$this->def);
}
//恢复角色状态
public function RecoveryState(RoleStateMemento $memento)
{
$this->vit = $memento->getVit();
$this->atk = $memento->getAtk();
$this->def = $memento->getDef();
}
}
RoleStateMemento.php
<?php
namespace DesignPatterns\Memento\v2;
/**
* 角色状态储存箱
* Class RoleStateMemento
* @package DesignPatterns\Memento\v2
*/
class RoleStateMemento
{
private $vit;//生命力
private $atk;//攻击力
private $def;//防御力
public function __construct($vit,$atk,$def)
{
$this->vit = $vit;
$this->atk = $atk;
$this->def = $def;
}
/**
* @param mixed $vit
*/
public function setVit($vit): void
{
$this->vit = $vit;
}
/**
* @return mixed
*/
public function getVit()
{
return $this->vit;
}
/**
* @param mixed $atk
*/
public function setAtk($atk): void
{
$this->atk = $atk;
}
/**
* @return mixed
*/
public function getAtk()
{
return $this->atk;
}
/**
* @param mixed $def
*/
public function setDef($def): void
{
$this->def = $def;
}
/**
* @return mixed
*/
public function getDef()
{
return $this->def;
}
}
RoleStateCaretaker.php
<?php
namespace DesignPatterns\Memento\v2;
/**
* 角色状态管理者类
* Class RoleStateCaretaker
* @package DesignPatterns\Memento\v2
*/
class RoleStateCaretaker
{
private $memento;
/**
* @param mixed $memento
*/
public function setMemento($memento): void
{
$this->memento = $memento;
}
/**
* @return mixed
*/
public function getMemento()
{
return $this->memento;
}
}
Client.php
<?php
namespace DesignPatterns\Memento\v2;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
public function run()
{
$gameRole = new GameRole();
//游戏角色初始状态,三项数据都是100
$gameRole->GetInitState();
$gameRole->StateDisplay();
//保存进度
//保存进度时,由于封装在Memento中,因此我们并不知道保存了哪些具体的角色数据
$RoleStateCaretaker = new RoleStateCaretaker();
$RoleStateCaretaker->setMemento($gameRole->SaveRoleState());
//大战boss,损失严重
$gameRole->Fight();
$gameRole->StateDisplay();
//恢复之前的状态
$gameRole->RecoveryState($RoleStateCaretaker->getMemento());
$gameRole->StateDisplay();
}
}
$worker = new Client();
$worker->run();
运行结果:
总结
备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。备忘录模式将代码中把要保存的细节给封装在了Memento中了,如果以后哪天要更改保存的细节也不会影响客户端了。
备忘录模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator 可以根据保存的Memento信息还原到前一状态。使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来。
github示例:https://github.com/yangpanyao/design-patterns/tree/master/Memento
评论 (0)