也称为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章