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(不只是存储代码,比如这一系统文章)。下回即将和我们见面的是桥接模式,不陌生吧,虚拟机上的网络配置就有桥接方式,那这货到底是干嘛的呢?且听下回分解。

各自媒体平台均可搜索【硬核项目经理】

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容