PHP設計模式之備忘錄模式

PHP設計模式之備忘錄模式

備忘錄,這個名字其實就已經很形象的解釋了它的做用。典型的例子就是咱們原來玩硬盤遊戲時的存檔功能。當你對即將面對的大BOSS有所顧慮時,通常都會先保存一次進度存檔。若是挑戰失敗了,直接讀取存檔就能夠恢復到挑戰BOSS前的狀態,而後你就開開心心的再去練一會級回來解決這個大BOSS就行了。不過,爲了以防萬一,在挑戰BOSS以前存個檔老是好的。另一個例子就是咱們碼農們每天要用到的代碼管理工具Git或者Svn了。每次的提交都像是一次存檔備份,當新代碼出現問題的時候,直接回滾恢復就好了。這些,都是備忘錄模式的典型應用,下面就一塊兒來看看這個模式吧。php

Gof類圖及解釋

GoF定義:在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態。這樣之後就可將該對象恢復到原先保存的狀態git

GoF類圖github

備忘錄模式

代碼實現數據庫

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()用於還原狀態(讀檔)。windows

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也是相似的功能。咱們的手機操做系統上也決定開發這樣的一個功能。當咱們點擊時光機備份時,將手機上全部的資料、數據、狀態信息都壓縮保存起來,若是用戶容許的話,咱們將這個壓縮包上傳到咱們的雲服務器上避免佔用用戶的手機內存,不然就只能保存到用戶的手機內存中了。當用戶的手機須要恢復到某個時間點,咱們將全部的時光機備份列出,用戶只須要用手指輕輕一按就能夠把手機系統狀態恢復到當時的樣子了,是否是很是方便!!服務器

完整代碼:github.com/zhangyue050…網絡

實例

此次又回到短信發送的例子上來。一般咱們作短信或者郵件發送這些功能時,會有一個隊列從數據庫或者緩存中讀取要發送的內容進行發送,若是成功了就無論了,若是失敗了會將短信的狀態改爲失敗或者重發。在這裏,咱們直接將它改回到以前未發送的狀態而後等待下次發送的隊列再次執行發送。

短信發送類圖

短信發送功能備忘錄模式版

完整源碼:github.com/zhangyue050…

<?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(不僅是存儲代碼,好比這一系統文章)。下回即將和咱們見面的是橋接模式,不陌生吧,虛擬機上的網絡配置就有橋接方式,那這貨究竟是幹嗎的呢?且聽下回分解。

相關文章
相關標籤/搜索