轉載自:
http://patmusing.blog.163.com/blog/static/13583496020101502024824/
也稱為Objects for States模式。
允許一個對象在其內部狀態改變時改變其行為,從而使對象看起來似乎修改了類。
“Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.” – GoF
一個應用的行為往往會根據其內部變量值而表現出很大的不同。比如用Visual Studio開發程序,當寫了一段程序后,如果點擊保存的按鈕,則所寫的程序便保存到了硬盤上,然后保存按鈕變灰,這時候程序文件時saved的狀態;在這個狀態上,如果有加了幾行代碼,那么狀態就變成unsaved,此時,保存按鈕又可以點擊。saved和unsaved是程序文件的狀態,保存這個動作是行為,對于saved或者unsaved,同樣的保存動作有不同的意義,即,如果程序文件時saved的狀態,那么點擊保存按鈕,則什么也不做(按鈕式灰色的);如果程序文件時unsaved狀態,點擊保存按鈕,則保存程序文件。這個案例說明,對象的狀態和其行為有密切的關系。
要解決類似上面的問題,通常的做法:
1. 設置一個狀態變量
2. 用switch – case或者if – else – if這樣的辦法來判定對象的狀態,然后根據所得到的狀態,選擇不同行為
如果增加了一個狀態,那么
1. 增加一個狀態變量
2. 修改switch – case或者if – else – if語句,這顯然不符合OCP的原則(這里會增加編譯的時間成本),也沒有把程序變化部分和相對穩定的部分隔離開,這會削弱應用程序的可擴展性
State模式就是為了解決上面的問題而產生的。即專門處理當對象處于不同的狀態時,將會有不同的行為這樣的情況。
State模式的UML類圖如下:

考慮一個門的狀態:

說明:
如果一個門處于Opened狀態,那么對其施加close動作,就會使門的狀態變成Closed的狀態;
如果一個門處于Opened狀態,那么對其施加open動作,那么門的狀態不會改變,依舊是Opened的狀態;
如果一個門處于Closed狀態,那么對其施加open動作,就會使門的狀態變成Opened的狀態;
如果一個門處于Closed狀態,那么對其施加close動作,那么門的狀態不會改變,依舊是Closed的狀態;
下面用State模式具體實現上面業務的C++代碼:
// State.h
#include <iostream>
#include <string>
#include <typeinfo>
#include <memory>
using namespace std;
// 前置聲明
class Door;
// 門的狀態
class DoorState
{
public:
virtual ~DoorState();
public:
virtual void open(Door* door) = 0; // 開門
virtual void close(Door* door) = 0; // 關門
};
// 打開狀態的門
class DoorOpened : public DoorState
{
public:
~DoorOpened();
public:
void open(Door* door);
void close(Door* door);
};
// 關閉狀態的門
class DoorClosed : public DoorState
{
public:
~DoorClosed();
public:
void open(Door* door);
void close(Door* door);
};
// 門
class Door
{
private:
auto_ptr<DoorState> door_state; // 門的狀態
public:
Door();
~Door();
public:
void open(); // 開門
void close(); // 關門
void set_state(auto_ptr<DoorState> door_state); // 設置門的狀態
string get_state(); // 獲取門的狀態
};
// State.cpp
#include "State.h"
DoorState::~DoorState()
{
cout << "in the destructor of DoorState..." << endl;
}
DoorOpened::~DoorOpened()
{
cout << "in the destructor of DoorOpened..." << endl;
}
// 如果門已經是打開的狀態,則僅輸出相關信息
void DoorOpened::open(Door* door)
{
cout << "The Door is already opened. State will not change..." << endl;
}
// 如果門是關閉的狀態,則關門
void DoorOpened::close(Door* door)
{
auto_ptr<DoorState> ds(new DoorClosed());
door->set_state(ds);
}
DoorClosed::~DoorClosed()
{
cout << "in the destructor of DoorClosed..." << endl;
}
// 如果門是關閉的狀態,則開門
void DoorClosed::open(Door* door)
{
auto_ptr<DoorState> ds(new DoorOpened());
door->set_state(ds);
}
// 如果門已經是關閉的狀態,則僅輸出相關信息
void DoorClosed::close(Door* door)
{
cout << "The Door is already closed. State will not change..." << endl;
}
Door::Door()
{
auto_ptr<DoorState> ds(new DoorOpened()); // 設定門的初始狀態
door_state = ds;
}
Door::~Door()
{
cout << "in the destructor of Door..." << endl;
}
void Door::open() // 開門
{
door_state->open(this);
}
void Door::close() // 關門
{
door_state->close(this);
}
void Door::set_state(auto_ptr<DoorState> door_state) // 設定門的狀態
{
this->door_state = door_state;
}
// 利用RTTI技術,輸出門的狀態
string Door::get_state()
{ // *(door_state.get())和*(door_state)的寫法是等價的,get()表示獲取auto_ptr對象中underlying對象的真實指針,因此語
// 法上顯得更好理解一些。但*在auto_ptr中也被重載過了,因此兩者的結果是一樣的。
string temp = typeid(*(door_state.get())).name(); // temp將等于 "class DoorOpened"或"class DoorClosed"
size_t found = temp.find("Door"); // 找到"Door"在temp中位置
temp = temp.substr(found + 4); // 去掉"Door"(包括其自身)前面的字符串
return temp;
}
// PatternClient.cpp
#include "State.h"
int main(int argc, char** argv)
{
Door door;
cout << "1. Initial state of the door: " << door.get_state() << endl;
cout << "------------------------------------------------------" << endl;
cout << endl;
door.open();
cout << "2. State after opening the door: " << door.get_state() << endl;
cout << "------------------------------------------------------" << endl;
cout << endl;
door.close();
cout << "3. State after closing the door: " << door.get_state() << endl;
cout << "------------------------------------------------------" << endl;
cout << endl;
door.close();
cout << "4. State after closing the door: " << door.get_state() << endl;
cout << "------------------------------------------------------" << endl;
cout << endl;
door.open();
cout << "5. State after opening the door: " << door.get_state() << endl;
cout << "------------------------------------------------------" << endl;
cout << endl;
return 0;
}
輸出結果:
1. Initial state of the door: Opened
------------------------------------------------------
The Door is already opened. State will not change...
2. State after opening the door: Opened
------------------------------------------------------
in the destructor of DoorOpened...
in the destructor of DoorState...
3. State after closing the door: Closed
------------------------------------------------------
The Door is already closed. State will not change...
4. State after closing the door: Closed
------------------------------------------------------
in the destructor of DoorClosed...
in the destructor of DoorState...
5. State after opening the door: Opened
------------------------------------------------------
in the destructor of Door...
in the destructor of DoorOpened...
in the destructor of DoorState...
上面程序的UML類圖如下:
要點:
- State設計模式將一個特定狀態相關的行為都放入一個State的子類對象中,在對象狀態切換時,切換相應的對象;但同時維持State的接口穩定,這樣實現了具體操作與狀態轉換之間的解耦。
- 為不同的狀態引入不同的對象,使得一個狀態和一個對象一一對應,這也是為什么這個模式也稱為Objects for States的原因。這使得狀態轉換變得更加明確,而且可以保證不會出現狀態不一致的情況,因為對象的狀態是由一個類來表示的,那么就保證了狀態轉換的原子性 – 即要么轉換成功,要不轉換 (即上面示例程序中的door_state要么被設定為DoorOpened,要么被設定為DoorClosed。如果一個對象有很多狀態,采用通常的方法會有很復雜的if-else if-else語句,維護這些復雜的語句,很有可能導致各種麻煩,其中之一就是狀態的不一致。)
- State模式和Strategy模式很類似。它們最主要的區別就是:State模式是用不同的對象去封裝不同的狀態,而Stategy模式用不同的對象去封裝不同的算法。
參考:
1. auto_ptr的用途和原理,詳見:http://patmusing.blog.163.com/blog/static/13583496020101824142699/
2. 使用auto_ptr的注意事項,詳見:http://patmusing.blog.163.com/blog/static/13583496020101824541270/
3. 關于RTTI,詳見Stan Lippman的《C++ Primer》第4版之第18章