PHP设计模式之备忘录模式
备忘录,这个名字其实就已经很形象的解释了它的作用。典型的例子就是我们原来玩硬盘游戏时的存档功能。当你对即将面对的大BOSS有所顾虑时,一般都会先保存一次进度存档。如果挑战失败了,直接读取存档就可以恢复到挑战BOSS前的状态,然后你就开开心心的再去练一会级回来解决这个大BOSS就好了。不过,为了以防万一,在挑战BOSS之前存个档总是好的。另外一个例子就是我们码农们天天要用到的代码管理工具Git或者Svn了。每次的提交都像是一次存档备份,当新代码出现问题的时候,直接回滚恢复就行了。这些,都是备忘录模式的典型应用,下面就一起来看看这个模式吧。
Gof类图及解释
GoF定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
GoF类图

代码实现
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()用于还原状态(读档)。
class Memento
{
private $state;
public function SetState($state)
{
$this->state = $state;
}
public function GetState()
{
return $this->state;
}
}
备忘录,非常简单,就是用于记录状态。将这个状态以对象的形式保存,就可以让原发器非常方便地创建很多存档用于记录各种不同的状态。
class Caretaker
{
private $memento;
public function SetMemento($memento)
{
$this->memento = $memento;
}
public function GetMemento()
{
return $this->memento;
}
}
负责人,也叫做管理者类,保存备忘录,当需要的时候从这里取出备忘录。它只负责保存,不能修改备忘录。在复杂的应用中,可以将这里做成列表,就像游戏中可以选择性的展现多条存档记录供玩家选择。
$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
<?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(不只是存储代码,比如这一系统文章)。下回即将和我们见面的是桥接模式,不陌生吧,虚拟机上的网络配置就有桥接方式,那这货到底是干嘛的呢?且听下回分解。
关注公众号:【硬核项目经理】获取最新文章
添加微信/QQ好友:【xiaoyuezigonggong/149844827】免费得PHP、项目管理学习资料
知乎、公众号、抖音、头条搜索【硬核项目经理】
B站ID:482780532
PHP设计模式之备忘录模式的更多相关文章
- 乐在其中设计模式(C#) - 备忘录模式(Memento Pattern)
原文:乐在其中设计模式(C#) - 备忘录模式(Memento Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 备忘录模式(Memento Pattern) 作者:webabc ...
- 折腾Java设计模式之备忘录模式
原文地址:折腾Java设计模式之备忘录模式 备忘录模式 Without violating encapsulation, capture and externalize an object's int ...
- C#设计模式:备忘录模式(Memento Pattern)
一,C#设计模式:备忘录模式(Memento Pattern) 1.发起人角色(Originator):记录当前时刻的内部状态,负责创建和恢复备忘录数据.负责创建一个备忘录Memento,用以记录当前 ...
- js设计模式——7.备忘录模式
js设计模式——7.备忘录模式 /*js设计模式——备忘录模式*/ // 备忘类 class Memento { constructor(content) { this.content = conte ...
- java设计模式之备忘录模式
备忘录模式 备忘录模式是一种软件设计模式:在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态.一听到备忘录这个字的时候想起了小小时打的游 ...
- 【GOF23设计模式】备忘录模式
来源:http://www.bjsxt.com/ 一.[GOF23设计模式]_备忘录模式.多点备忘.事务操作.回滚数据底层架构 package com.test.memento; /** * 源发器类 ...
- [设计模式] 18 备忘录模式Memento Pattern
在GOF的<设计模式:可复用面向对象软件的基础>一书中对备忘录模式是这样说的:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存 ...
- 再起航,我的学习笔记之JavaScript设计模式24(备忘录模式)
备忘录模式 概念介绍 备忘录模式(Memento): 在不破坏对象的封装性的前提下,在对象之外捕获并保存该对象内部的状态以便日后对象使用或者对象恢复到以前的某个状态. 简易分页 在一般情况下我们需要做 ...
- Head First设计模式之备忘录模式
一.定义 不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样就可以将该对象恢复到原先保存的状态 二.结构 备忘录模式中主要有三类角色: 发起人角色:记录当前时刻的内部状态, ...
- 【Unity与23种设计模式】备忘录模式(Memento)
GoF中定义: "在不违反封装的原则下,获取一个对象的内部状态并保留在外部,让对象可以在日后恢复到原先保留时的状态." 对于一些需要存储的数据,比如历史最高分 当与得分减分系统写入 ...
随机推荐
- CAS5.3服务器搭建与客户端整合SpringBoot以及踩坑笔记
CAS5.3服务器搭建与客户端整合SpringBoot以及踩坑笔记 cas服务器的搭建 导出证书(1和2步骤是找了课程,随便写了一下存记录,不过对于自己测试不投入使用应该不影响) C:\Users\D ...
- Git出错:“Please make sure you have the correct access rights and the repository exists.”
此问题是需要重置ssh密钥 解决步骤如下: 1.重置用户名和邮箱: 打开Git Bash 进入Git命令,输入以下命令 git config --global user.name "你的用户 ...
- 【笔记】模型泛化与岭回归与LASSO
模型泛化与岭回归与LASSO 模型正则化 模型正则化,简单来说就是限制参数大小 模型正则化是用什么思路来解决先前过拟合的由于过于拟合导致的曲线抖动(线性方程前的系数都很大) 线性回归的目标就是求一个最 ...
- [08 Go语言基础-for循环]
[08 Go语言基础-for循环] 循环 循环语句是用来重复执行某一段代码. for 是 Go 语言唯一的循环语句.Go 语言中并没有其他语言比如 C 语言中的 while 和 do while 循环 ...
- 【Java笔记】以并发修改异常为例总结的出错解决办法
先来看出错代码: /*需求: 遍历已有集合 如果在集合中发现存在字符串元素"world" 则在"world"后添加元素"javaee" */ ...
- Ubuntu安装arm-linux-gcc 步骤
文章目录 1.获取压缩包 2.解压 压缩包到指定路径 3.新建目录,解放目录权限 4.复制到新目录 5.配置环境变量和库变量 6.使用source命令重新加载生效该配置文件 7.验证 gcc编译的只能 ...
- SQL 练习16
按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩 SELECT * from SC LEFT JOIN (SELECT sid,AVG(score) 平均成绩 from SC GROUP B ...
- SQL 练习11
查询至少有一门课与学号为" 01 "的同学所学相同的同学的信息 SELECT * from Student WHERE SId in (SELECT SId from sc WHE ...
- 教你使用ApiPost中的全局参数和目录参数
前面的示例中,我们都是在单一接口中填入不同的请求header.query.body参数.但在实际项目中,对于一批接口,往往具有相同的请求参数.此时,我们可以利用全局参数或者目录参数实现. 例如:常见的 ...
- @CreatedDate@CreatedBy@LastModifiedBy@LastModifiedDate
启动类上加上@EnableJpaAuditing 实体类,注意需要加上@EntityListeners(AuditingEntityListener.class)这个注解才能使@CreatedDate ...