設計模式專題(十三) ——備忘錄模式
設計模式專題(十三)——備忘錄模式
(原創內容,轉載請註明來源,謝謝)
一、概述
備忘錄模式(Memento)是在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態,這樣就可以將該物件恢復到原先儲存的狀態。
備忘錄模式將儲存的細節封裝在備忘錄中,當程式變動需要改動儲存細節,也不需要客戶端進行改動。該模式適合場景複雜,但是需要維護或記錄屬性歷史的類。並且,通常不需要全量儲存,可以通過儲存的部分資料恢復整個細節。
另外,備忘錄模式可以把儲存的資料進行加密,則保證儲存的資料完整性。
備忘錄模式使用場景較多,如文字編輯的撤銷、棋類遊戲的悔棋、單機遊戲的存檔等,都會用到備忘錄模式。
二、類圖
三、具體設計
備忘錄模式中有三個角色。
1、發起人Originator,是被儲存的類,其可以自定義方式讓使用者進行儲存。另外,需要提供解析已經儲存的資料,實現恢復資料的功能。
2、備忘錄Memento,用於生成originator以某種方式儲存後的資料。
3、管理者Caretaker,其不可以操作或者檢視memento的細節,僅用於管理當前儲存的各種備忘錄,供客戶端呼叫。
四、實現方式
用PHP實現備忘錄模式,可以用到php的內部魔術方法__sleep()和__wakeup。當儲存類的時候,通常會用serialize,則__sleep()可以控制序列化哪些內容。
php實現上述三種角色,方式如下:
1、Originator,類的狀態,提供當前狀態、選擇性儲存、解析儲存的狀態。需要使用__sleep()和__wakeup;但也可以自定義儲存方式,如果要自定義儲存,則需要把__sleep()和__wakeup的返回值都設定成null,避免外界使用serialize來進行儲存。
2、Memento,主要是類儲存後的加密與解密,為了保證儲存的資料不被外界改動,在經過備忘錄的時候,進行加密和解密,保證資料的安全性。另外,還有一個很重要的功能,就是獲取類儲存的功能,供管理者類呼叫。
3、Caretaker,根據客戶端的要求,呼叫Memento獲取到originator的儲存以及加密後的內容,並存儲在本地檔案或資料庫等地方;根據客戶端的恢復要求,去本地檔案或資料庫中查詢資料,並呼叫memento獲取解密和解析後的結果,返回給客戶端。
五、程式實現(關鍵內容)
class Originator{
private$piecePosArr;//三維陣列,第一維是第幾步,第二維是每個棋子和其位置的map,第三維是棋子的位置
publicfunction __construct(){
$piecePosArr= array(
'step1'=> array(array('piece1' => 'pos1'),array('piece2' => 'pos2')...),
'step2'=> array(array('piece1' => 'pos1'),array('piece2' => 'pos2')...),
//......
);
}
publicfunction __sleep(){
$pieces= $this->piecePosArr;
if(!empty($pieces)){
foreach($piecesas &$piece){
//....進行相應操作
}
}
return$pieces;
}
publicfunction __wakeup(){
}
}
//Memento
class Memento{
private$orig;
privateconst $secret = array(
'a','b', 'c', 'd', 'e', 'f', 'g', //...到z
);
privateconst CODE = 'my_memento';
privatefunction getEnCode($len){
//對服務端和客戶端儲存的內容分別加密,
//並把服務端的內容儲存在伺服器(作為鑰匙),把客戶端的內容儲存在客戶端
//這樣如果客戶端私自篡改資料,服務端的資料也會無法解析客戶端的資料,即保證資料的安全
returnarray(
'client'=> 'toclient',
'service'=> 'toservice'
);
}
//加密
publicfunction saveData(Originator $orig, $userId){
$this->orig= $orig;
//加密,在陣列中加入內容,加密的內容儲存在本地進行校驗
$arrSecret= $this->getEnCode(10);
//儲存在服務端的redis,省略redis連線部分
$redis->set("chees:$userId:key",$arrSecret['service']);
return$arrSecret['client'];
}
//解析
publicfunction recoverData($encryptData, $clientCode){
}
}
//caretaker
class CareTaker{
publicfunction saveData(Originator $orig, $userId){
$memento= new Memento();
$res= $memento->saveData($orig, $userId);
//....獲取的結果存在客戶端的本地檔案,根據userid->值的方式進行儲存
}
publicfunction recoverData($userId){
//.....根據userid從使用者本地取得data的內容
$memento= new Memento();
$res= $memento->recoverData($data, $userId);
}
}
上述程式僅實現關鍵部分,備忘錄在特定應用場景下非常實用。
——written by linhxx 2017.08.10
相關閱讀: