大衛的Design Patterns學習筆記18:Memento

一、概述
Memento(備忘錄)模式在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。這樣以後就可將該對象恢復到原先保存的狀態。

二、結構
Memento模式的類圖結構如下圖所示:

1、Memento模式類圖示意
Memento模式所涉及的角色有三個,備忘錄角色、發起人角色和負責人角色。
其中:
Memento(備忘錄):負責存儲原發器對象的內部狀態,並可防止原發器以外的其他對象訪問備忘錄。
Originator(原發器):負責創建一個備忘錄,用以記錄當前時刻它的內部狀態,並可使用備忘錄恢復內部狀態。原發器可根據需要決定備忘錄存儲原發器的哪些內部狀態,因此,當需要保存全部信息時,可以考慮用clone的方式來實現Memento的狀態保存方法,但是如果是這樣的話,我們有時候可能會考慮不使用Memento,而是直接保存Originator本身,但這樣使得我們相當於對上層應用開放了Originator的全部(public)接口,這對於保存備份有時候是不必要的。
Caretaker(負責人):負責保存好備忘錄,並且往往不能對備忘錄的內容進行操作或檢查。

備忘錄實際上有兩個接口,Caretaker只能看到備忘錄的窄接口,即它只能將備忘錄傳遞給其他對象。而原發器能夠看到一個寬接口,允許它訪問返回到先前狀態所需的所有數據。理想的情況是隻允許生成本備忘錄的那個原發器訪問本備忘錄的內部狀態。

舉一個實際的例子(非Software Application),以Windows系統備份爲例:備份(Memento)是備忘錄角色、Windows系統(WindowsSystem)類是發起人角色、用戶(User)類是負責人角色。用戶不關心備份的內部細節,而且也無法直接對備份的內容進行直接修改,但Windows系統則可以(在用戶指定的情況下)決定備份什麼內容,以及如何還原備份。

三、應用
以下情況下可考慮使用Memento模式:
1
、必須保存一個對象在某一個時刻的(部分)狀態,這樣以後需要時它才能恢復到先前的狀態;
2
、如果一個用接口來讓其它對象直接得到這些狀態,將會暴露對象的實現細節並破壞對象的封裝性。

四、優缺點
Memento模式有以下這些優缺點:
1
、保持封裝邊界 使用備忘錄可以避免暴露一些只應由原發器管理卻又必須存儲在原發器之外的信息。該模式把可能很複雜的Originator內部信息對其他對象屏蔽起來,從而保持了封裝邊界。
2
、它簡化了原發器 在其他的保持封裝性的設計中,Originator負責保持客戶請求過的內部狀態版本。這就把所有存儲管理的重任交給了Originator。讓客戶管理它們請求的狀態將會簡化Originator,並且使得客戶工作結束時無需通知原發器。
3
、使用備忘錄可能代價很高 如果原發器在生成備忘錄時必須拷貝並存儲大量的信息,或者客戶非常頻繁地創建備忘錄和恢復原發器狀態,可能會導致非常大的開銷。除非封裝和恢復Originator狀態的開銷不大,否則該模式可能並不合適。
4
、維護備忘錄的潛在代價 管理器負責刪除它所維護的備忘錄。然而,管理器不知道備忘錄中有多少個狀態。因此當存儲備忘錄時,一個本來很小的管理器,可能會產生大量的存儲開銷。

五、舉例
正如結構部分所說,我們有時候可能會考慮直接保存Originator本身,而不是另外抽象出一個Memento類,就好像在Command模式筆記中舉的AdjustUndoableEdit的例子一樣,保存的是實際的ModelObject的clone,而不是另外寫一個Memento類來。
因此,Memento模式往往被我們忽略,但Memento模式的主要作用在於職責的分離,同時隱藏Originator的實現細節,並在有些情況下起到簡化Originator的作用。
因爲Memento模式具有的優點在很多情況下並不爲我們所關係,同時,還會由此引入更復雜的類結構及可能引入更大的管理開銷,因此,是否需要引入Memento模式及何時引入Memento模式是一個需要認真考慮的問題,個人認爲,Memento模式比較適用於功能比較複雜的,但需要維護或記錄屬性歷史的類,對於這樣的類,對Caretaker公開Originator的接口顯得有點多餘,或者需要保存的屬性只是衆多屬性中的一小部分時(或者根本就不是Originator的屬性,但是由Originator引起,並且Originator可以根據保存的Memento信息還原到前一狀態,如GoF的DP一書中提到的增量約束解釋工具QOCA),爲了節約存儲空間,也可以考慮使用Memento模式(這種情況下,clone就太奢侈,也太不負責任了,:))。
以下示例取自參考1
// 1. Assign the roles of "caretaker" and "originator"
// 2. Create a "memento" class and declare the originator a friend
// 3. Caretaker knows when to "check point" the originator
// 4. Originator creates a memento and copies its state to the memento
// 5. Caretaker holds on to (but cannot peek in to) the memento
// 6. Caretaker knows when to "roll back" the originator
// 7. Originator reinstates itself using the saved state in the memento

#include <iostream>
#include <string>
using namespace std;

class
 Memento {                   // 2. Create a "memento" class and
    friend class Stack;            //    declare the originator a friend
    int *items, num;
    Memento( int* arr, int n ) {
        items = new int[num = n];
        for
 (int i=0; i < num; i++) items[i] = arr[i]; }
public
:
    ~
Memento() { delete items; }
};


class
 Stack {                     // 1. Stack is the "originator"
    int  items[10], sp;
public
:
    Stack()                 { sp = -1; }
    void
     push( int in ) { items[++sp] = in; }
    int
      pop()          { return items[sp--]; }
    bool
     isEmpty()      { return sp == -1; }
    // 4. Originator creates a memento and copies its state to the memento
    Memento* checkPoint() {
        return
 new Memento( items, sp+1 );
    }

    // 7. Originator reinstates itself using the saved state in the memento
    void rollBack( Memento* m ) {
        sp = m->num-1;
        for
 (int i=0; i < m->num; i++) items[i] = m->items[i];
    }

    friend
 ostream& operator<< ( ostream& os,  const Stack& s ) {
        string buf( "[ " );
        for
 (int i=0; i < s.sp+1; i++) { buf += s.items[i]+48;  buf += ' '; }
        buf += ']';
        return
 os << buf;
    }
};


// 1. main() is the "caretaker"
void main( void ) {
    Stack s;
    int
 i = 0;
    for
 (i=0; i < 5; i++) s.push( i );
    cout << "stack is " << s << endl;
    
    Memento* first = s.checkPoint();       // 3. Caretaker knows when to save
    
    for
 (i=5; i < 10; i++) s.push( i );    // 5. Caretaker holds on to memento
    cout << "stack is " << s << endl;
    
    Memento* second = s.checkPoint();      // 3. Caretaker knows when to save
    
    cout << "popping stack: ";             // 5. Caretaker holds on to memento
    while ( ! s.isEmpty()) cout << s.pop() << ' ';  cout << endl;
    
    cout << "stack is " << s << endl;
    
    s.rollBack( second );                  // 6. Caretaker knows when to undo
    cout << "second is " << s << endl;
    
    s.rollBack( first );                   // 6. Caretaker knows when to undo    
    cout << "first is " << s << endl;
    
    cout << "popping stack: ";
    while
 ( ! s.isEmpty()) cout << s.pop() << ' ';  cout << endl;
    
    delete
 first;  delete second;
}


參考:
1
、http://home.earthlink.net/~huston2/dp/MementoDemosCpp
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章