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中定义: "在不违反封装的原则下,获取一个对象的内部状态并保留在外部,让对象可以在日后恢复到原先保留时的状态." 对于一些需要存储的数据,比如历史最高分 当与得分减分系统写入 ...
随机推荐
- SpringBoot开发十五-发布帖子
需求介绍 使用 AJAX 异步通信实现网页能够增量的更新呈现到页面上而不需要刷新整个页面. 现在基本上都是服务器返回 JSON 字符串来解析 代码实现 使用 JQuery 发送 AJAX 请求. 首先 ...
- SpringBoot开发九-生成验证码
需求介绍-生成验证码 先生成随机字符串然后利用Kaptcha API生成验证图片 代码实现 先在pom.xml引入 <dependency> <groupId>com.gith ...
- JavaSE-基础语法
注释 单行注释: //注释 多行注释: /*注释*/ 文档注释: /** 文档注释 */ 标识符与关键字 下图为Java中所有的关键字 所有标识符必须以大小写字母或$或_开头,首字母之后可以用数字 不 ...
- 博客CSS样式 二
预览 可自行更改颜色 背景图 页面定制 CSS 代码中加入: url为背景图地址,可下载心仪背景图后上传到博客园相册后获取地址 body { color: #000; background: url( ...
- SwiftUI图片处理(缩放、拼图)
采用SwiftUI Core Graphics技术,与C#的GDI+绘图类似,具体概念不多说,毕竟我也是新手,本文主要展示效果图及代码,本文示例代码需要请拉到文末自取. 1.图片缩放 完全填充,变形压 ...
- miniFTP项目实战六
项目简介: 在Linux环境下用C语言开发的Vsftpd的简化版本,拥有部分Vsftpd功能和相同的FTP协议,系统的主要架构采用多进程模型,每当有一个新的客户连接到达,主进程就会派生出一个ftp服务 ...
- Quartz任务调度(1)概念例析快速
实例解析概念 在quartz中,有几个核心类和接口:Job.JobDetail.Trigger.Calendar.Scheduler.下面我们结合实例来分析这些类的角色定位.现在我们有一个新闻网站,它 ...
- 【AE】多表的联合查询
多表的联合查询 // Create the query definition. IQueryDef queryDef = featureWorkspace.CreateQueryDef(); // P ...
- [转]VRRP协议详解
原文地址:VRRP协议详解 文中涉及缩略语 缩略语 英文全名 中文解释 VRRP Virtual Router Redundancy Protocol 虚拟路由器冗余协议 NQA Network Qu ...
- git推送文件到gitee
注册gitee账号 设置姓名.个人空间地址 点击头像旁边的加号,新建仓库 安装git # 设置姓名和邮箱,姓名是注册gitee时设置的姓名,邮箱是注册gitee的邮箱 git config --glo ...