备忘录,这个名字其实就曾经很形象的解释了它的作用。典型的例子就是咱们原来玩硬盘游戏时的存档性能。当你对行将面对的大BOSS有所顾虑时,个别都会先保留一次进度存档。如果挑战失败了,间接读取存档就能够复原到挑战BOSS前的状态,而后你就开开心心的再去练一会级回来解决这个大BOSS就好了。不过,为了以防万一,在挑战BOSS之前存个档总是好的。另外一个例子就是咱们码农们天天要用到的代码管理工具Git或者Svn了。每次的提交都像是一次存档备份,当新代码呈现问题的时候,间接回滚复原就行了。这些,都是备忘录模式的典型利用,上面就一起来看看这个模式吧。
Gof类图及解释
GoF定义:在不毁坏封装性的前提下,捕捉一个对象的外部状态,并在该对象之外保留这个状态。这样当前就可将该对象复原到原先保留的状态
GoF类图
代码实现
<code class="php">class Originator { private $state; public function SetMeneto(Memento $m) { $this->state = $m->GetState(); } public function CreateMemento() { $m = new Memento(); $m->SetState($this->state); return $m; } public function SetState($state) { $this->state = $state; } public function ShowState() { echo $this->state, PHP_EOL; } }
原发器,也能够叫做发起人。它有一个外部状态(state),这个状态能够在不同的状况下进行扭转。当某一个事件产生时,须要将这个状态复原到原先的状态。在这里,咱们有一个CreateMemento()用于创立一个备忘录(存档),有一个SetMeneto()用于还原状态(读档)。
<code class="php">class Memento { private $state; public function SetState($state) { $this->state = $state; } public function GetState() { return $this->state; } }
备忘录,非常简单,就是用于记录状态。将这个状态以对象的模式保留,就能够让原发器十分不便地创立很多存档用于记录各种不同的状态。
<code class="php">class Caretaker { private $memento; public function SetMemento($memento) { $this->memento = $memento; } public function GetMemento() { return $this->memento; } }
负责人,也叫做管理者类,保留备忘录,当须要的时候从这里取出备忘录。它只负责保留,不能批改备忘录。在简单的利用中,能够将这里做成列表,就像游戏中能够选择性的展示多条存档记录供玩家抉择。
<code class="php">$o = new Originator(); $o->SetState('状态1'); $o->ShowState(); // 保留状态 $c = new Caretaker(); $c->SetMemento($o->CreateMemento()); $o->SetState('状态2'); $o->ShowState(); // 还原状态 $o->SetMeneto($c->GetMemento()); $o->ShowState();
客户端的调用中,咱们的原发器初始化状态后进行了保留,而后人为的更改了状态。这时只须要通过负责人将状态还原回来就能够了。
- 备忘录模式说白了就是让一个外部类B来保留A的外部状态,而后在适当的时候能够不便的还原这个状态。
- 备忘录模式的利用场景其实十分多,浏览器的回退、数据库的备份还原、操作系统的备份还原、文档的撤销重做、棋牌游戏的悔棋等等
- 这个模式可能放弃对原发器的封装,也就是这些状态须要对外部的对象暗藏,所以只能交给一个备忘录对象来记录
- 状态在原发器和备忘录之间的拷贝可能带来性能问题,特地是大型对象的简单繁多的外部状态,而且也会带来一些编码方面的破绽,比方漏掉某些状态
Mac的时光机性能大家有理解过吧,能够将电脑复原到某一时间点的状态下。其实windows的ghost也是相似的性能。咱们的手机操作系统上也决定开发这样的一个性能。当咱们点击时光机备份时,将手机上所有的材料、数据、状态信息都压缩保存起来,如果用户容许的话,咱们将这个压缩包上传到咱们的云服务器上防止占用用户的手机内存,否则就只能保留到用户的手机内存中了。当用户的手机须要复原到某个工夫点,咱们将所有的时光机备份列出,用户只须要用手指微微一按就能够把手机零碎状态复原到过后的样子了,是不是十分不便!!
残缺代码:https://github.com/zhangyue0503/designpatterns-php/blob/master/17.memento/source/memento.php
实例
这次又回到短信发送的例子上来。通常咱们做短信或者邮件发送这些性能时,会有一个队列从数据库或者缓存中读取要发送的内容进行发送,如果胜利了就不论了,如果失败了会将短信的状态改成失败或者重发。在这里,咱们间接将它改回到之前未发送的状态而后期待下次发送的队列再次执行发送。
短信发送类图
残缺源码:https://github.com/zhangyue0503/designpatterns-php/blob/master/17.memento/source/memento-message.php
<code class="php"><?php class Message { private $content; private $to; private $state; private $time; public function __construct($to, $content) { $this->to = $to; $this->content = $content; $this->state = '未发送'; $this->time = time(); } public function Show() { echo $this->to, '---', $this->content, '---', $this->time, '---', $this->state, PHP_EOL; } public function CreateSaveSate() { $ss = new SaveState(); $ss->SetState($this->state); return $ss; } public function SetSaveState($ss) { if ($this->state != $ss->GetState()) { $this->time = time(); } $this->state = $ss->GetState(); } public function SetState($state) { $this->state = $state; } public function GetState() { return $this->state; } } class SaveState { private $state; public function SetState($state) { $this->state = $state; } public function GetState() { return $this->state; } } class StateContainer { private $ss; public function SetSaveState($ss) { $this->ss = $ss; } public function GetSaveState() { return $this->ss; } } // 模仿短信发送 $mList = []; $scList = []; for ($i = 0; $i < 10; $i++) { $m = new Message('手机号' . $i, '内容' . $i); echo '初始状态:'; $m->Show(); // 保留初始信息 $sc = new StateContainer(); $sc->SetSaveState($m->CreateSaveSate()); $scList[] = $sc; // 模仿短信发送,2发送胜利,3发送失败 $pushState = mt_rand(2, 3); $m->SetState($pushState == 2 ? '发送胜利' : '发送失败'); echo '公布后状态:'; $m->Show(); $mList[] = $m; } // 模仿另一个线程查找发送失败的并把它们还原到未发送状态 sleep(2); foreach ($mList as $k => $m) { if ($m->GetState() == '发送失败') { // 如果是发送失败的,还原状态 $m->SetSaveState($scList[$k]->GetSaveState()); } echo '查问公布失败后状态:'; $m->Show(); }
阐明
- 短信类做为咱们的原发器,在发送前就保留了以后的发送状态
- 随机模仿短信发送,只有两个状态,发送胜利或者失败,并扭转原发器的状态为胜利或者失败
- 模仿另一个线程或者脚本对短信的发送状态进行查看,如果发现有失败的,就将它从新改回未发送的状态
- 这里咱们只是保留了发送状态这一个字段,其余原发器的外部属性并没有保留
- 实在的场景下咱们应该会有一个重试次数的限度,当超过这个次数后,状态改为彻底的发送失败,不再进行重试了
下期看点
备忘录模式就是这样咱们平时天天都在用的模式,说是备忘,不如说是悔恨模式更贴切些。人生没有后悔药,但程序世界里能够有,还是那句话,养成备份重要文件、材料、代码的好习惯,灵便应用Git(不只是存储代码,比方这一零碎文章)。下回行将和咱们见面的是桥接模式,不生疏吧,虚拟机上的网络配置就有桥接形式,那这货到底是干嘛的呢?且听下回分解。
各自媒体平台均可搜寻【硬核项目经理】