某个对象有一个标准接口。同时,该对象可能处于各种状态下。当该对象处于不同状态下时,客户调用标准接口,该对象会产生不同的响应。
即允许一个对象在其内部状态改变时改变它的行为,使对象看起来似乎修改了它的类。这就是状态模式。
例如,电灯开关这个对象提供了一个标准接口:按下开关。而开关可能处于关闭/打开这两种状态。当开关处于关闭状态时,调用标准接口按下开关,电灯会被打开;当开关处于打开状态时,调用标准接口按下开关,电灯会被关闭。
即当开关处于不同状态时,对其执行相同的操作,返回的结果也不同。
状态模式需要定义如下对象:
① Context:环境,即上面例子中的开关对象。用于定义提供给客户的标准接口。
Context内部会存储一个State指针,用以保存当前状态。
② State:状态基类,定义一组所有状态所影响到的接口。具体实现延迟到子类。
注意State所定义的这些接口实际上是从Context中分离出的功能。
若将对State的判断及执行对应效果都放在Context中,Context会非常臃肿,且结构不清晰。对State进行修改时也非常麻烦。
所以将Context与状态相关的功能都分离出来,并将这些功能延迟到State的子类进行实现。这样,当Context需要执行某个功能时,调用State成员变量来执行即可。
③ ConcreteState:状态派生类,有多个,针对于Context的每一种状态,都有一个对应的ConcreteState类。实现了State中的相关接口。
这样,用户每次直接与Context进行交互,然后Context会调用自身的State成员变量指针来执行指定的效果。
还是使用上面电灯开关的例子。电灯开关即Context,含一个标准接口:按下开关。电灯的关闭/打开状态为两个State:ConcreteStateClose,ConcreteStateOpen。其中,State含有两个效果函数:打开电灯/关闭电灯,以及一个接口函数:按下开关。电灯默认状态为关闭状态。
当用户调用Context的按下开关接口时,Context会调用当前状态ConcreteStateClose的按下开关接口,从而ConcreteStateClose将令电灯打开;
当用户再次调用Context的按下开关接口时,Context会调用当前状态ConcreteStateOpen的按下开关接口,从而ConcreteStateOpen将令电灯关闭。
1. State基类及派生类
State基类定义了标准接口。该标准接口是从Context中分离出的功能。
State派生类实现接口,根据自身的不同,而有不同的实现。
注意下面的代码将Context的指针内嵌到了State派生类中。这是为了State执行某些与Context相关的操作时方便调用Context的接口。如果State执行的功能相对独立,不需要与Context进行交互,那么可以不用将Context指针嵌入。
//预定义Context类
class Context;
//状态基类
class State
{
public:
virtual void operation() = 0;
};
//状态派生类
class ConcreteStateA : public State
{
public:
ConcreteStateA(Context* pContext) : _pContext(pContext) {}
virtual void operation()
{
std::cout << "ConcreteStateA::operation()" << std::endl;
}
private:
Context* _pContext;
};
//状态派生类
class ConcreteStateB : public State
{
public:
ConcreteStateB(Context* pContext) : _pContext(pContext) {}
virtual void operation()
{
std::cout << "ConcreteStateB::operation()" << std::endl;
}
private:
Context* _pContext;
};
2. 环境类
环境类将所有类型的状态都内嵌到自身,并维护一个当前状态指针。当改变状态时,直接修改当前状态指针即可。
当调用环境类的标准接口时,环境类会调用当前状态的标准接口,从而用户间接调用了状态类的标准接口。最终功能由状态类实现。
//环境类
class Context
{
public:
Context()
{
_pConcreteStateA = new ConcreteStateA(this);
_pConcreteStateB = new ConcreteStateB(this);
_pCurrentState = _pConcreteStateA;
}
void enterStateA() { _pCurrentState = _pConcreteStateB; }
void enterStateB() { _pCurrentState = _pConcreteStateA; }
void operation() { _pCurrentState->operation(); }
private:
void setState(State* pContextMode) { _pCurrentState = pContextMode; }
private:
ConcreteStateB* _pConcreteStateB;
ConcreteStateA* _pConcreteStateA;
State* _pCurrentState;
};
3. 用户使用
由于各种状态由环境类负责创建,所以用户只需要创建并操作环境类即可。
void main()
{
Context context;
context.enterStateA();
context.operation();
context.enterStateB();
context.operation();
}
注意上面 Context进入某种状态是由用户来调用的。根据实际需求不同,可以将状态的转换封装到 Context类中,这样用户只需要调用标准接口,状态转换由 Context内部负责。
例如前面的电灯开关例子,开关状态的转换就应该由开关类负责,而非用户。
状态模式将所有状态进行了分离,减少了耦合;同时将与特定状态相关的行为局部化;通过定义State派生类,可以很容易地添加新的状态并进行转换。
但是状态模式必然会增加系统类和对象的个数。使用不当有可能会导致结构的混乱。