1.动机

  • 在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象,这样的对象状态是从事先定义好的一系列值中取出的。当一个这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化

在实际开发中,经常会遇到这种情况;一个对象有多种状态,在每一个状态下,都会有不同的行为。

typedef enum tagState
{
     state,
     state1,
     state2
}State;

void Action(State actionState)
{
     if (actionState == state)
     {
          // DoSomething
     }
     else if (actionState == state1)
     {
          // DoSomething
     }
     else if (actionState == state2)
     {
          // DoSomething
     }
     else
     {
          // DoSomething
     }
}

而这种就好比简单工厂模式,当我们增加新的状态类型时,我们又需要修改原来的代码,这种对于测试是很不利的;由于简单工厂的缺点那么的明显,后来的工厂模式就克服了这个缺点,我们就可以借鉴工厂模式,来解决这种随着状态增加而出现的多分支结构。

2.定义

  • 状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。
  • 状态模式的重点在于状态转换,很多时候,对于一个对象的状态,我们都是让这个对象包含一个状态的属性,这个状态属性记录着对象的具体状态,根据状态的不同使用分支结构来执行不同的功能,就像上面的代码那样处理
  • 就像上面说的,类中存在大量的结构类似的分支语句,变得难以维护和理解。状态模式消除了分支语句,就像工厂模式消除了简单工厂模式的分支语句一样,将状态处理分散到各个状态子类中去,每个子类集中处理一种状态,这样就使得状态的处理和转换清晰明确

3.结构

clipboard.png

  • Context:定义客户端感兴趣的接口,并且维护一个ConcreteState子类的实例,这个实例定义当前状态;
  • State:定义一个接口以封装与Context的一个特定状态相关的行为;
  • ConcreteState subclasses每一个子类实现一个与Context的一个状态相关的行为。

它们之间的协作步骤如下:

  • Context将与状态相关的请求委托给当前的ConcreteState对象处理;
  • Context可以将自身作为一个参数传递给处理该请求的状态对象。这使得状态对象在必要时可以访问Context;
  • Context是客户使用的主要接口。客户可用状态对象来配置一个Context,一旦一个Context配置完毕,它的客户不再需要直接与状态对象打交道;

4.代码分析

#include <iostream>
using namespace std;

class Context;

class State
{
public:
    virtual void Handle(Context *pContext) = 0;
};

class Context
{
public:
    Context(State *pState) : m_pState(pState){}

    //根据状态,表现处不同的行为
    void Request()
    {
        if (m_pState)
        {
            m_pState->Handle(this);
        }
    }

    //改变当前状态
    void ChangeState(State *pState)
    {
        m_pState = pState;
    }

private:
    State *m_pState;
};

//具体状态类,根据不同的状态实现不同的行为
class ConcreteStateA : public State
{
public:
    //状态对象在必要时可以访问Context;
    virtual void Handle(Context *pContext)
    {
        cout << "I am concretestateA." << endl;
    }
};

//具体状态类,根据不同的状态实现不同的行为
class ConcreteStateB : public State
{
public:
    //状态对象在必要时可以访问Context
    virtual void Handle(Context *pContext)
    {
        State *pStateA = new ConcreteStateA();
        cout << "I am concretestateB." << endl;

        //状态B不会停留,完成某些操作,自动转换到状态A上去执行操作
        pContext->ChangeState(pStateA);
    }
};



int main()
{
    State *pStateA = new ConcreteStateA();
    State *pStateB = new ConcreteStateB();
    Context *pContext = new Context(pStateA);
    pContext->Request();//状态A

    pContext->ChangeState(pStateB);//状态B
    pContext->Request();//状态B所属行为执行完毕之后,状态转换到A
    pContext->Request();//执行状态A所属行为
    
    delete pContext;
    delete pStateB;
    delete pStateA;

    system("pause");
    return 0;
}

5.模式分析

  • 状态模式描述了对象状态的变化以及对象如何在每一种状态下表现出不同的行为。
  • 状态模式的关键是引入了一个抽象类来专门表示对象的状态,这个类我们叫做抽象状态类,而对象的每一种具体状态类都继承了该类,并在不同具体状态类中实现了不同状态的行为,包括各种状态之间的转换。

在状态模式结构中需要理解环境类与抽象状态类的作用:

  • 环境类实际上就是拥有状态的对象,环境类有时候可以充当状态管理器(State Manager)的角色,可以在环境类中对状态进行切换操作。
  • 抽象状态类可以是抽象类,也可以是接口,不同状态类就是继承这个父类的不同子类,状态类的产生是由于环境类存在多个状态,同时还满足两个条件:

    • 这些状态经常需要切换,在不同的状态下对象的行为不同。因此可以将不同对象下的行为单独提取出来封装在具体的状态类中,使得环境类对象在其内部状态改变时可以改变它的行为,对象看起来似乎修改了它的类,而实际上是由于切换到不同的具体状态类实现的。
    • 由于环境类可以设置为任一具体状态类,因此它针对抽象状态类进行编程,在程序运行时可以将任一具体状态类的对象设置到环境类中,从而使得环境类可以改变内部状态,并且改变行为。

6.优点

  • 封装了转换规则。
  • 枚举可能的状态,在枚举状态之前需要确定状态种类。
  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
  • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

7.缺点

  • 状态模式的使用必然会增加系统类和对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  • 状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。

8.适用环境

  • 对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为。
  • 代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。在这些条件语句中包含了对象的行为,而且这些条件对应于对象的各种状态。

9.type_traits

  • 上面讲的状态模式主要还是利用的运行时对状态的解析,利用多态实现。执行文件的代码量不会减少。
  • 而type_traits是编译期就去确定具体的类型,从而根据不同的类型来执行不同的模块,消除重复,提高代码质量

燃烧你的梦
238 声望17 粉丝