“狀態變化”模式
在組建構建過程中,某些對象的狀態經常面臨變化,如何對這些變化進行有效的管理?同時又維持高層模塊的穩定?“狀態變化”模式為這一個問題提供了一種解決方案。
- 典型模式
- State
- Memento
狀態模式(State)
允許一個對象在其內部狀態改變是改變它的行為。從而使對像看起來似乎修改其行為。
——《設計模式》GoF
- 動機
在軟件構建過程中,某些對象的狀態如果改變,其行為也會隨之而發生變化,比如文檔處于只讀狀態,其支持的行為和讀寫狀態支持的行為就可能會完全不同。
假設有一個網絡的應用,移動有三種狀態,打開、關閉和連接。
操作針對不同的狀態會執行不同的行為。(狀態不同,行為不同)
同時執行一次操作完畢后,也會更改狀態。
enum NetworkState
{
Network_Open,
Network_Close,
Network_Connect,
};
class NetworkProcessor{
NetworkState state;
public:
void Operation1(){
if (state == Network_Open){
//**********
state = Network_Close;
}
else if (state == Network_Close){
//..........
state = Network_Connect;
}
else if (state == Network_Connect){
//$$$$$$$$$$
state = Network_Open;
}
}
public void Operation2(){
if (state == Network_Open){
//**********
state = Network_Connect;
}
else if (state == Network_Close){
//.....
state = Network_Open;
}
else if (state == Network_Connect){
//$$$$$$$$$$
state = Network_Close;
}
}
public void Operation3(){
}
};
對于上面的代碼來說,未來狀態是否會發生變化?如果出現了新的變化,那么之后的所有的代碼,全部都需要改變,違背了開閉原則。
那么根據Strategy模式的啟發,我們把狀態的向上抽象,每個自己實現出來的狀態,將所有跟狀態有關的操作全都變成具體的狀態對像的行為。每個狀態也可以次啊用單例的模式。每個狀態的操作之后,可以根據他的操作完后,在操作的方法中,將自動定義下一個狀態。對于調用者來說,每次的狀態改變只需要將指針指向操作所改變的下一個對象即可,那么使用者完全不需要考慮下一個狀態是什么,因為在制定狀態的操作中已經改變好了。
class NetworkState{
public:
NetworkState* pNext;
virtual void Operation1()=0;
virtual void Operation2()=0;
virtual void Operation3()=0;
virtual ~NetworkState(){}
};
class OpenState :public NetworkState{
static NetworkState* m_instance;
public:
static NetworkState* getInstance(){
if (m_instance == nullptr) {
m_instance = new OpenState();
}
return m_instance;
}
void Operation1(){
//**********
pNext = CloseState::getInstance();
}
void Operation2(){
//..........
pNext = ConnectState::getInstance();
}
void Operation3(){
//$$$$$$$$$$
pNext = OpenState::getInstance();
}
};
class CloseState:public NetworkState{ }
//...
class NetworkProcessor{
NetworkState* pState;
public:
NetworkProcessor(NetworkState* pState){
this->pState = pState;
}
void Operation1(){
//...
pState->Operation1();
pState = pState->pNext;
//...
}
void Operation2(){
//...
pState->Operation2();
pState = pState->pNext;
//...
}
void Operation3(){
//...
pState->Operation3();
pState = pState->pNext;
//...
}
};
要點總結
- State模式將所有與一個特定狀態相關的行為都放入一個State的子類對象中,在對像狀態切換時, 切換相應的對象;但同時維持State的接口,這樣實現了具體操作與狀態轉換之間的解耦。
- 為不同的狀態引入不同的對象使得狀態轉換變得更加明確,而且可以保證不會出現狀態不一致的情況,因為轉換是原子性的——即要么徹底轉換過來,要么不轉換。
- 如果State對象沒有實例變量,那么各個上下文可以共享同一個State對象,從而節省對象開銷。
備忘錄(Memento)
在不破壞封裝性的前提下,不活一個對象的內部狀態,并在該對像之外保存這個狀態。這樣以后就可以將該對像恢復到原想保存的狀態。
——《設計模式》GoF
- 動機
在軟件構建過程中,某些對象的狀態在轉會過程中,可能由于某種需求,要求程序能夠回溯到對像之前處于某個點時的狀態。如果使用一些公有借口來讓其它對象得到對象的狀態,便會暴露對象的實現細節。
class Memento
{
string state;
//..
public:
Memento(const string & s) : state(s) {}
string getState() const { return state; }
void setState(const string & s) { state = s; }
};
class Originator
{
string state;
//....
public:
Originator() {}
Memento createMomento() {
Memento m(state);
return m;
}
void setMomento(const Memento & m) {
state = m.getState();
}
};
int main()
{
Originator orginator;
//捕獲對象狀態,存儲到備忘錄
Memento mem = orginator.createMomento();
//... 改變orginator狀態
//從備忘錄中恢復
orginator.setMomento(memento);
}
假設Originator是一個需要被保存的對象,他的內部可能有很多的狀態,我們有一個需求:在某一個時間節點,為這個對象拍一個快照。那么我們設計了一個Memento對象,其中具有和Originator需要備份的屬性。Memento具有供外界調用的接口。那么被創建的備份,應該是穩定的,外界只有讀取信息的結構,沒有修改信息的接口。
在Orginator還應該具有從Memento恢復的方法,來依靠之前所存儲的快照進行恢復。
以上僅僅是一個示意性的偽碼描述,也可以通過其他的手段來實現Memento的過程,比如對象的序列化和反序列化等等。
要點總結
備忘錄(Memento)存儲原發器(Originator)對象的內部狀態,在需要時恢復原發器的狀態。
Memento模式的核心是信息隱藏,即Originator需要向外接隱藏信息,保持其封裝性。但同時又需要將其狀態保持到外界(Memento)
由于現代語言運行時(如C#、java等)都具有相當的對象序列化支持,因此往往采用效率較高、有較容易正確實現的序列化方案來實現Memento模式。
由于《設計模式》是在94年定義的,現在很多的技術發展已經變化,現在Memento的實現方法已經過時了,現在在使用其他的方式在做這件事,但是對于思想并沒有發生變化。在備份的過程中,還需要保持封裝的時候,將信息保存到外部。