備忘錄模式與訪問者模式
備忘錄模式
Memento模式也叫備忘錄模式,是行為模式之一,它的作用是儲存物件(原生物件)的內部狀態到備忘錄,並在需要的時候通過caretaker(儲存了備忘錄)提取備忘錄的內容(undo/rollback)恢復原生物件以前的狀態。
角色及職責
Originator(原生者)
需要被儲存狀態以便恢復的那個物件。
Memento(備忘錄)
該物件由Originator建立,主要用來儲存Originator的內部狀態。
Caretaker(管理者)
負責在適當的時間儲存/恢復Originator物件的狀態。
適用於:
在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態,這樣就可以將以後的物件狀態恢復到先前儲存的狀態。
適用於功能比較複雜的,但需要記錄或維護屬性歷史的類;或者需要儲存的屬性只是眾多屬性中的一小部分時Originator可以根據儲存的Memo還原到前一狀態。
案例
如果一個物件需要儲存狀態並可通過undo或rollback等操作恢復到以前的狀態時,可以使用Memento模式。
1)一個類需要儲存它的物件的狀態(相當於Originator角色)
2)設計一個類,該類只是用來儲存上述物件的狀態(相當於Memento角色)
3)需要的時候,Caretaker角色要求Originator返回一個Memento並加以儲存
4)undo或rollback操作時,通過Caretaker儲存的Memento恢復Originator物件的狀態
/*******************************
*某個物件也許在將來有一天會恢復到現如今的狀態,
*那麼就必須找一個備忘錄儲存現如今的狀態,以便找得到恢復的依據
*
*原生類需要依賴備忘錄儲存自己的資訊
* --所以需要建立這個備忘錄--函式返回值是備忘錄
*
* 備忘錄和原生類的屬性幾乎一樣--很顯然是為了記錄原生類啊
*
*管理者需要儲存備忘錄的內容--實際上儲存的就是原生物件的內部狀態
* --需要備忘錄做成員變數(關聯)
* --建構函式需要備忘錄做引數(依賴)
* --設定函式需要備忘錄做引數
*
*******************************/
#include <iostream>
#include <string>
using namespace std;
/*備忘錄--存放源物件的內部狀態--不打破封裝性*/
class Memto
{
public:
Memto(string name,int age)//建構函式建立備忘錄
{
m_name = name;
m_age = age;
}
string getName()//獲取快取的屬性--姓名
{
return m_name;
}
int getAge()//獲取快取的屬性--年齡
{
return m_age;
}
private:
/*具有和原生類一樣的屬性*/
string m_name;
int m_age;
};
/*待儲存的原生類--能夠建立備忘錄*/
class Person
{
public:
Person(string name,int age)//建構函式構造原生物件
{
m_name = name;
m_age = age;
}
string getName()//獲取屬性
{
return m_name;
}
int getAge()//獲取屬性
{
return m_age;
}
void setName(string name)//設定屬性
{
m_name = name;
}
void setAge(int age)//設定屬性
{
m_age = age;
}
Memto * createMemto()//建立一個備忘錄--目的是傳遞給管理者
{
return new Memto(m_name,m_age);
}
void setMemto(Memto * memto)//恢復內部狀態--根據之前的備忘錄恢復自己的狀態--這個備忘錄從管理者獲取
{
m_name = memto->getName();
m_age = memto->getAge();
}
void printPerson()//列印資訊
{
cout<<"name : "<<m_name << "age : "<<m_age<<endl;
}
private:
/*自己的私有屬性--備忘錄需要具有相關的屬性--以便記錄*/
string m_name;
int m_age;
};
/*管理者*/
class CareTaker
{
public:
CareTaker(Memto *mem)//利用備忘錄構造管理者--接收已建立好的備忘錄--儲存備忘錄
{
m_mem = mem;
}
/*根據已有的備忘錄設定自己的備忘錄成員--實質上就是進行儲存備忘錄的操作*/
void setmem(Memto *mem)
{
m_mem = mem;
}
/*獲取備忘錄--將備忘錄提供給想要恢復狀態的原生物件*/
Memto *getMem()
{
return m_mem;
}
private:
Memto * m_mem;//儲存的備忘錄
};
/*測試案例*/
int main()
{
Person * p = new Person("simon",22);//原生物件
CareTaker * care = NULL;//管理者
p->printPerson();
care = new CareTaker(p->createMemto());//原生物件建立備忘錄
//管理者儲存該備忘錄
p->setAge(42);//原生物件內部狀態發生改變
p->printPerson();
/*管理者提供自己已經儲存的備忘錄
*原生物件根據他提供的備忘錄進行恢復
*/
p->setMemto(care->getMem());
p->printPerson();
return 0;
}
訪問者模式
Visitor模式也叫訪問者模式,是行為模式之一,它分離物件的資料和行為,使用Visitor模式,可以不修改已有類的情況下,增加新的操作角色和職責。
訪問者模式特點:
訪問者模式優點是增加新的操作很容易,因為增加新的操作就意味著增加一個新的訪問者。訪問者模式將有關的行為集中到一個訪問者物件中。
那訪問者模式的缺點是是增加新的資料結構變得困難了
角色和職責
抽象訪問者(Visitor)角色:聲明瞭一個或者多個訪問操作,形成所有的具體元素角色必須實現的介面。
具體訪問者(ConcreteVisitor)角色:實現抽象訪問者角色所宣告的介面,也就是抽象訪問者所宣告的各個訪問操作。
抽象節點(Element)角色:宣告一個接受操作,接受一個訪問者物件作為一個參量。
具體節點(ConcreteElement)角色:實現了抽象元素所規定的接受操作。
結構物件(ObiectStructure)角色:有如下的一些責任,可以遍歷結構中的所有元素;如果需要,提供一個高層次的介面讓訪問者物件可以訪問每一個元素;如果需要,可以設計成一個複合物件或者一個聚集,如列(List)或集合(Set)。
適用於:
把資料結構 和 作用於資料結構上的操作 進行解耦合;
適用於資料結構比較穩定的場合
優缺點
訪問者模式有如下的優點:
1,訪問者模式使得增加新的操作變得很容易。如果一些操作依賴於一個複雜的結構物件的話,那麼一般而言,增加新的操作會很複雜。而使用訪問者模式,增加新的操作就意味著增加一個新的訪問者類,因此,變得很容易。
2,訪問者模式將有關的行為集中到一個訪問者物件中,而不是分散到一個個的節點類中。
3,訪問者模式可以跨過幾個類的等級結構訪問屬於不同的等級結構的成員類。迭代子只能訪問屬於同一個型別等級結構的成員物件,而不能訪問屬於不同等級結構的物件。訪問者模式可以做到這一點。
4,積累狀態。每一個單獨的訪問者物件都集中了相關的行為,從而也就可以在訪問的過程中將執行操作的狀態積累在自己內部,而不是分散到很多的節點物件中。這是有益於系統維護的優點。
訪問者模式有如下的缺點:
1,增加新的節點類變得很困難。每增加一個新的節點都意味著要在抽象訪問者角色中增加一個新的抽象操作,並在每一個具體訪問者類中增加相應的具體操作。
2,破壞封裝。訪問者模式要求訪問者物件訪問並呼叫每一個節點物件的操作,這隱含了一個對所有節點物件的要求:它們必須暴露一些自己的操作和內部狀態。不然,訪問者的訪問就變得沒有意義。由於訪問者物件自己會積累訪問操作所需的狀態,從而使這些狀態不再儲存在節點物件中,這也是破壞封裝的。
案例
比如有一個公園,有一到多個不同的組成部分;該公園存在多個訪問者:清潔工A負責打掃公園的A部分,清潔工B負責打掃公園的B部分,公園的管理者負責檢點各項事務是否完成,上級領導可以視察公園等等。也就是說,對於同一個公園,不同的訪問者有不同的行為操作,而且訪問者的種類也可能需要根據時間的推移而變化(行為的擴充套件性)。
根據軟體設計的開閉原則(對修改關閉,對擴充套件開放),我們怎麼樣實現這種需求呢?
#include <iostream>
#include <string>
#include <list>
using namespace std;
class ParkElement;//前置宣告
/*抽象訪問者*/
class Visitor
{
public:
virtual void visit(ParkElement * park_elem) = 0;
};
/*抽象節點*/
class ParkElement
{
public:
virtual void accept(Visitor * v) = 0;
string getName()
{
return m_name;
}
protected:
string m_name;
};
/*具體節點*/
class ParkA : public ParkElement
{
public:
ParkA()
{
m_name = "ParkA";
}
virtual void accept(Visitor * v)
{
v->visit(this);
}
};
/*具體節點*/
class ParkB : public ParkElement
{
public:
ParkB()
{
m_name = "ParkB";
}
virtual void accept(Visitor * v)
{
v->visit(this);
}
};
/*具體訪問者--不同訪問者代表不同的操作*/
class VisitorA : public Visitor
{
public:
virtual void visit(ParkElement * park_elem)
{
cout<<"VisitorA Do"<<park_elem->getName()<<endl;
}
};
/*具體訪問者--不同訪問者代表不同的操作*/
class VisitorB : public Visitor
{
public:
virtual void visit(ParkElement * park_elem)
{
cout<<"VisitorB Do"<<park_elem->getName()<<endl;
}
};
/*具體節點--代表所有節點的集合--是一個比較特殊的具體節點*/
class Park: public ParkElement
{
public:
Park()
{
m_list.clear();
}
void setParkElement(ParkElement * pe)
{
m_list.push_back(pe);
}
virtual void accept(Visitor * v)//遍歷訪問
{
for(list<ParkElement*>::iterator it = m_list.begin();it != m_list.end();it++)
{
v->visit(*it);
}
}
private:
list<ParkElement*> m_list;//公園的每一部分--應該讓每一部分都讓管理者訪問
};
/*具體訪問者--代表一個管理員--針對所有的具體節點都可以進行操作*/
class Manager:public Visitor
{
public:
virtual void visit(ParkElement * park_elem)
{
cout<<"Manager Do"<<park_elem->getName()<<endl;
}
private:
};
/*一個具體節點接受一個具體訪問者--低效--沒有完全實現資料結構和操作的完全分離*/
void play()
{
Visitor *vA = new VisitorA;
Visitor *vB = new VisitorB;
ParkElement *pA = new ParkA;
ParkElement *pB = new ParkB;
pA->accept(vA);
pB->accept(vB);
delete vA;
delete vB;
delete pA;
delete pB;
}
/*實現了多型
實現了資料結構和操作的完全分離*/
void play02()
{
Visitor *vM = new Manager;
ParkElement *pA = new ParkA;
ParkElement *pB = new ParkB;
Park * park = new Park;
park->setParkElement(pA);
park->setParkElement(pB);
park->accept(vM);
delete vM;
delete park;
delete pA;
delete pB;
}
/*測試案例*/
int main()
{
//play();
play02();
return 0;
}