轉(zhuǎn)載自:
http://patmusing.blog.163.com/blog/static/13583496020101501825958/
Aka. Token
在不破壞封裝性的前提下,捕獲一個(gè)對象的內(nèi)部狀態(tài),并在該對象之外保存其狀態(tài)。這樣以后就可以將該對象恢復(fù)到原先保存的狀態(tài)。
“Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.” – GoF
動機(jī)
有時(shí)候需要記錄一個(gè)對象的內(nèi)部狀態(tài)。比如要實(shí)現(xiàn)checkpoint或者undo這樣的機(jī)制,可以讓使用者從臨時(shí)性的操作跳出來或者需要修復(fù)錯(cuò)誤的時(shí)候,你必須將狀態(tài)信息保存在某個(gè)地方,以便在進(jìn)行某些操作后,將對象恢復(fù)到原來的狀態(tài)。但通常情況下,對象封裝了狀態(tài)(即私有成員變量),因此其他的對象無法訪問這些狀態(tài),而且也不可能將這些狀態(tài)保存在對象之外。如果將這些狀態(tài)設(shè)置成公有的,又會違反面向?qū)ο蠓庋b性的原則,同時(shí)也會削弱應(yīng)用的可靠性和可擴(kuò)展性。
在軟件構(gòu)建過程中, 某些對象的狀態(tài)在轉(zhuǎn)換過程中,可能由于某種需要,要求程序能夠回溯到對象之前處于某個(gè)時(shí)刻的狀態(tài)。如果使用一些共有接口來讓其他對象得到對象的狀態(tài),便會暴露對象的細(xì)節(jié)實(shí)現(xiàn)。Memento設(shè)計(jì)模式就可以實(shí)現(xiàn)對象狀態(tài)的良好保存與恢復(fù),但同時(shí)又不會因此而破壞對象本身的封裝性。
UML類圖:

角色
- Memento
1. 保存Originator對象的內(nèi)部狀態(tài)。
2. 除Originator外,其他對象均不能訪問Memento對象。
- Originator
1. 創(chuàng)建一個(gè)包含其當(dāng)前內(nèi)部狀態(tài)快照的Memento對象。
2. 使用Memento對象來恢復(fù)其內(nèi)部狀態(tài)。
- Caretaker
1. Memento對象的容器。在C++中一般用stack來實(shí)現(xiàn)。
2. 從不對Memento對象的內(nèi)容進(jìn)行檢查或操作。
示例代碼:
// Memento.h
#include <iostream>
#include <stack>
using namespace std;
// CMemento類,用來保存CRectangle的狀態(tài)
class CMemento
{
private: // 所有的成員變量和成員函數(shù)都是私有的
int topx; // 因此除友元類CRectangle外,其他對象都無法訪問
int topy;
int width;
int height;
private:
CMemento()
{
}
//private: // 1. 如果編寫了顯式拷貝構(gòu)造函數(shù),那么,它必須是公有的,
// CMemento(const CMemento& memo) // 否則CMementoStack將無法調(diào)用該拷貝構(gòu)造函數(shù)。
// { // 2. 如果沒有顯式的拷貝構(gòu)造函數(shù),那么缺省的拷貝構(gòu)造函數(shù)總是公有的。
// topx = memo.topx; // 3. 在Memento模式中,如果僅考慮保存一次狀態(tài),則
// topy = memo.topy; // CMementoStack是不必要的,那么拷貝構(gòu)造函數(shù),可以
// width = memo.width; // 顯式地聲明為private的,盡管在CRectangle的create_memento
// height = memo.height; // 成員函數(shù)中也會調(diào)用CMemento的拷貝構(gòu)造函數(shù),但CRectangle
// } // 是CMemento的友元類,因此不存在這方面的限制。
private:
void set_state(int topx, int topy, int width, int height) // 保存CRectangle的狀態(tài)
{
this->topx = topx;
this->topy = topy;
this->width = width;
this->height = height;
}
friend class CRectangle; // 友元類CRectangle,可以訪問CMemento中的所有內(nèi)容
};
// CRectangle類。一個(gè)矩形,需要保存狀態(tài)改變的類
class CRectangle
{
private:
int topx; // 矩形左上角的x坐標(biāo)
int topy; // 矩形左上角的y坐標(biāo)
int width; // 矩形的寬
int height; // 矩形的高
public:
CRectangle(int topx, int topy, int width, int height):topx(topx), topy(topy), width(width), height(height)
{
}
// 模擬移動矩形的位置到指定的點(diǎn),即改變了矩形的狀態(tài)
void move_to(int topx, int topy)
{
this->topx = topx;
this->topy = topy;
}
// 模擬改變矩形的長和寬,即改變了矩形的狀態(tài)
void change_width_height(int width, int height)
{
this->width = width;
this->height = height;
}
// 將矩形恢復(fù)到memo中所保存的狀態(tài)
void set_memento(CMemento memo)
{
this->topx = memo.topx;
this->topy = memo.topy;
this->width = memo.width;
this->height = memo.height;
}
// 將矩形的狀態(tài)保存到一個(gè)CMemento對象
CMemento create_memento()
{
CMemento cm;
cm.set_state(this->topx, this->topy, this->width, this->height);
return cm;
}
// 輸出矩形的狀態(tài)信息
void print_info()
{
cout << "Top left point's x coordinate: " << topx << endl;
cout << "Top left point's y coordinate: " << topy << endl;
cout << "The width is: " << width << endl;
cout << "The height is: " << height << endl;
}
};
// CMemento對象的容器,可以用來保存多個(gè)CMemento對象,通常用stack來實(shí)現(xiàn)
class CMementoStack
{
private:
stack<CMemento> stk;
public:
void add_memento(CMemento memo)
{
stk.push(memo); //將CMemento對象壓入棧中
}
CMemento get_memento()
{
CMemento cm = stk.top(); // 取得CMemento對象。這個(gè)過程會用到CMemento類的拷貝構(gòu)造函數(shù),
// 由于CMemento對象中的成員變量均是普通類型(非指針、非類對象),
// 因此使用默認(rèn)的拷貝構(gòu)造函數(shù)即可
stk.pop(); // 刪除已經(jīng)取得的CMemento對象
return cm;
}
};
// Memento.cpp
#include "Memento.h"
int main(int argc, char **argv)
{
CRectangle cr(10, 10, 100, 100);
CMementoStack cs;
cout << "Initial states: " << endl;
cr.print_info();
CMemento cm0 = cr.create_memento(); // 將狀態(tài)保存到CMemento對象
cs.add_memento(cm0); // 將CMemento對象壓棧
// 第一次改變狀態(tài)
cr.change_width_height(200, 200); // 改變矩形的高度和寬度
cr.move_to(20, 20); // 改變矩形的位置
cout << "\nAfter 1st states changed: " << endl;
cr.print_info();
CMemento cm1 = cr.create_memento(); // 將狀態(tài)保存到CMemento對象
cs.add_memento(cm1); // 將CMemento對象壓棧
// 第二次改變狀態(tài)
cr.change_width_height(300, 300); // 改變矩形的高度和寬度
cr.move_to(30, 30); // 改變矩形的位置
cout << "\nAfter 2nd states changed: " << endl;
cr.print_info();
// ... 這里不再壓棧
// 恢復(fù)到第一次狀態(tài)的改變
cr.set_memento(cs.get_memento());
cout << "\nStates restored to 1st change: " << endl;
cr.print_info();
// 恢復(fù)到初始狀態(tài)
cr.set_memento(cs.get_memento());
cout << "\nStates restored to initial: " << endl;
cr.print_info();
}
運(yùn)行結(jié)果:
Initial states:
Top left point's x coordinate: 10
Top left point's y coordinate: 10
The width is: 100
The height is: 100
After 1st states changed:
Top left point's x coordinate: 20
Top left point's y coordinate: 20
The width is: 200
The height is: 200
After 2nd states changed:
Top left point's x coordinate: 30
Top left point's y coordinate: 30
The width is: 300
The height is: 300
States restored to 1st change:
Top left point's x coordinate: 20
Top left point's y coordinate: 20
The width is: 200
The height is: 200
States restored to initial:
Top left point's x coordinate: 10
Top left point's y coordinate: 10
The width is: 100
The height is: 100
結(jié)果符合預(yù)期。
補(bǔ)充說明:
1. 未設(shè)置其成員變量為public的前提下,在對象的外部保存一個(gè)其狀態(tài),頗需技巧,而且用不同語言來實(shí)現(xiàn)的時(shí)候也有所不同。
a. 對于C++,通常使用友元類來實(shí)現(xiàn)。
b. 對于C#,使用internal關(guān)鍵字。
c. 對于Java,使用package protected訪問控制。
與其處于相同包中的子類,和處于相同包中的其它類均可以訪問pakcage protected的對象或變量。
Java中的訪問權(quán)限有public,private,protected和默認(rèn)的包訪問權(quán)限,如果類中的屬性方法沒有顯示的指明訪問權(quán)
限,則具有包訪問權(quán)限,很多人也稱它為friendly訪問權(quán)限,也有人稱為packeged權(quán)限,而packaged和friendly
這兩個(gè)關(guān)鍵字在實(shí)際中都是不存在的。在Java中,訪問權(quán)限修飾符權(quán)限從高到低是public,protected,package
protected,private。
d. C++, C#和Java均可使用內(nèi)部類的方式來實(shí)現(xiàn)類似的功能,不過對于“將狀態(tài)存儲于對象之外”而言,稍嫌勉強(qiáng)。
2. 關(guān)于拷貝構(gòu)造函數(shù),請看:http://patmusing.blog.163.com/blog/static/1358349602009113061024796/
3. 關(guān)于友元類,請看:http://patmusing.blog.163.com/blog/static/1358349602010182331153/