State:状态模式

论坛 期权论坛 脚本     
匿名技术用户   2020-12-22 06:02   45   0

某个对象有一个标准接口。同时,该对象可能处于各种状态下。当该对象处于不同状态下时,客户调用标准接口,该对象会产生不同的响应。

允许一个对象在其内部状态改变时改变它的行为,使对象看起来似乎修改了它的类。这就是状态模式。

例如,电灯开关这个对象提供了一个标准接口:按下开关。而开关可能处于关闭/打开这两种状态。当开关处于关闭状态时,调用标准接口按下开关,电灯会被打开;当开关处于打开状态时,调用标准接口按下开关,电灯会被关闭。

即当开关处于不同状态时,对其执行相同的操作,返回的结果也不同。

状态模式需要定义如下对象:

① 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派生类,可以很容易地添加新的状态并进行转换。

但是状态模式必然会增加系统类和对象的个数。使用不当有可能会导致结构的混乱。

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP