1. 程式人生 > 其它 >設計模式專題(十三) ——備忘錄模式

設計模式專題(十三) ——備忘錄模式

設計模式專題(十三)——備忘錄模式

(原創內容,轉載請註明來源,謝謝)

一、概述

備忘錄模式(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

相關閱讀:

設計模式專題(十二)——狀態模式

設計模式專題(十一)——抽象工廠模式

設計模式專題(十)——觀察者模式

設計模式專題(九) ——外觀模式

設計模式專題(八) ——模板方法模式

設計模式專題(七)——建造者模式

設計模式專題(六)——原型模式

設計模式專題(五)——工廠方法模式

設計模式專題(四)——代理模式

設計模式專題(三)——裝飾模式

設計模式專題(二)——策略模式

設計模式專題(一)——面向物件的設計原則