備忘錄,這個名字其實就已經很形象的解釋了它的作用,典型的例子就是我們原來玩硬碟游戲時的存檔功能,當你對即將面對的大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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/242689.html
標籤:PHP
上一篇:WEB前端第五十三課——Xampp、Apache、PHP基礎⑴
下一篇:Python中常用魔術方法
