对象如何在不同状态下表现出不同的行为?

解决方案

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

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

适用性

在下面的两种情况下均可使用State模式:

  1. 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
  2. 代码中包含大量与对象状态有关的条件语句:一个操作中含有庞大的多分支的条件(if else(或switch case)语句,且这些分支依赖于该对象的状态。

结构

state

模式的组成

  • 环境类(Context): 定义客户感兴趣的接口。维护一个ConcreteState子类的实例,这个实例定义当前状态。
  • 抽象状态类(State): 定义一个接口以封装与Context的一个特定状态相关的行为。
  • 具体状态类(ConcreteState): 每一子类实现一个与Context的一个状态相关的行为。

效果

State模式有下面一些效果:

状态模式的优点

  1. 它将与特定状态相关的行为局部化,并且将不同状态的行为分割开来: State模式将所有与一个特定的状态相关的行为都放入一个对象中。因为所有与状态相关的代码都存在于某一个State子类中, 所以通过定义新的子类可以很容易的增加新的状态和转换。另一个方法是使用数据值定义内部状态并且让 Context操作来显式地检查这些数据。但这样将会使整个Context的实现中遍布看起来很相似的条件if else语句或switch case语句。增加一个新的状态可能需要改变若干个操作, 这就使得维护变得复杂了。State模式避免了这个问题, 但可能会引入另一个问题, 因为该模式将不同状态的行为分布在多个State子类中。这就增加了子类的数目,相对于单个类的实现来说不够紧凑。但是如果有许多状态时这样的分布实际上更好一些, 否则需要使用巨大的条件语句。正如很长的过程一样,巨大的条件语句是不受欢迎的。它们形成一大整块并且使得代码不够清晰,这又使得它们难以修改和扩展。 State模式提供了一个更好的方法来组织与特定状态相关的代码。决定状态转移的逻辑不在单块的 i f或s w i t c h语句中, 而是分布在State子类之间。将每一个状态转换和动作封装到一个类中,就把着眼点从执行状态提高到整个对象的状态。这将使代码结构化并使其意图更加清晰。

  2. 它使得状态转换显式化: 当一个对象仅以内部数据值来定义当前状态时 , 其状态仅表现为对一些变量的赋值,这不够明确。为不同的状态引入独立的对象使得转换变得更加明确。而且, State对象可保证Context不会发生内部状态不一致的情况,因为从 Context的角度看,状态转换是原子的—只需重新绑定一个变量(即Context的State对象变量),而无需为多个变量赋值

  3. State对象可被共享: 如果State对象没有实例变量—即它们表示的状态完全以它们的类型来编码—那么各Context对象可以共享一个State对象。当状态以这种方式被共享时, 它们必然是没有内部状态, 只有行为的轻量级对象。

状态模式的缺点:

  1. 状态模式的使用必然会增加系统类和对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。

实现

代码实现:

#include <iostream>
using namespace std;

class Context;

/**
 * 定义一个电梯的接口
 */
class LiftState
{
public:
    void setContext(Context* context)
    {
        _context = context;
    }

    //首先电梯门开启动作
    virtual void open() = 0;

    //电梯门有开启,那当然也就有关闭了
    virtual void close() = 0;

    //电梯要能上能下,跑起来
    virtual void run() = 0;

    //电梯还要能停下来,停不下来那就扯淡了
    virtual void stop() = 0;

protected:
    //定义一个环境角色,也就是封装状态的变换引起的功能变化
    Context* _context;
};

/**
 * 环境类:定义客户感兴趣的接口。维护一个ConcreteState子类的实例,这个实例定义当前状态。
 */
class Context
{
public:
    //定义出所有的电梯状态
    static LiftState* openningState;
    static LiftState* closeingState;
    static LiftState* runningState;
    static LiftState* stoppingState;

    Context()
    {
        _liftState = NULL;
    }

    LiftState* getLiftState()
    {
        return _liftState;
    }

    void setLiftState(LiftState* liftState)
    {
        _liftState = liftState;
        //把当前的环境通知到各个实现类中
        _liftState->setContext(this);
    }

    void open()
    {
        _liftState->open();
    }

    void close()
    {
        _liftState->close();
    }

    void run()
    {
        _liftState->run();
    }

    void stop()
    {
        _liftState->stop();
    }

private:
    //定一个当前电梯状态
    LiftState* _liftState;
};


/**
 * 在电梯门开启的状态下能做什么事情
 */
class OpenningState:public LiftState
{
public:
    /**
     * 开启当然可以关闭了,我就想测试一下电梯门开关功能
     */
    void close()
    {
        //状态修改
        _context->setLiftState(Context::closeingState);
        //动作委托为CloseState来执行
        _context->getLiftState()->close();
    }

    //打开电梯门
    void open()
    {
        cout <<  "lift open..." << endl;
    }

    //门开着电梯就想跑,这电梯,吓死你!
    void run()
    {
        //do nothing;
    }

    //开门还不停止?
    void stop()
    {
        //do nothing;
    }
};

/**
 * 电梯门关闭以后,电梯可以做哪些事情
 */
class ClosingState: public LiftState
{
public:
    //电梯门关闭,这是关闭状态要实现的动作
    void close()
    {
        cout << "lift close..." << endl;
    }

    //电梯门关了再打开,逗你玩呢,那这个允许呀
    void open()
    {
        _context->setLiftState(Context::openningState);  //置为门敞状态
        _context->getLiftState()->open();
    }

    //电梯门关了就跑,这是再正常不过了
    void run()
    {
        _context->setLiftState(Context::runningState); //设置为运行状态;
        _context->getLiftState()->run();
    }

    //电梯门关着,我就不按楼层
    void stop()
    {
        _context->setLiftState(Context::stoppingState);  //设置为停止状态;
        _context->getLiftState()->stop();
    }
};

/**
 * 电梯在运行状态下能做哪些动作
 */
class RunningState: public LiftState
{
public:
    //电梯门关闭?这是肯定了
    void close()
    {
        //do nothing
    }

    //运行的时候开电梯门?你疯了!电梯不会给你开的
    void open()
    {
        //do nothing
    }

    //这是在运行状态下要实现的方法
    void run()
    {
        cout << "lift run..." << endl;
    }

    //这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了
    void stop()
    {
        _context->setLiftState(Context::stoppingState); //环境设置为停止状态;
        _context->getLiftState()->stop();
    }
};

/**
 * 在停止状态下能做什么事情
 */
class StoppingState: public LiftState
{
public:
    //停止状态关门?电梯门本来就是关着的!
    void close()
    {
        //do nothing;
    }

    //停止状态,开门,那是要的!
    void open()
    {
        _context->setLiftState(Context::openningState);
        _context->getLiftState()->open();
    }
    //停止状态再跑起来,正常的很
    void run()
    {
        _context->setLiftState(Context::runningState);
        _context->getLiftState()->run();
    }
    //停止状态是怎么发生的呢?当然是停止方法执行了
    void stop()
    {
        cout << "lift stop..." << endl;
    }
};

//初始化state
LiftState* Context::openningState = new OpenningState();
LiftState* Context::closeingState = new ClosingState();
LiftState* Context::runningState =  new RunningState();
LiftState* Context::stoppingState = new StoppingState();

/**
 * 模拟电梯的动作
 */
int main()
{
    Context* context = new Context();
    context->setLiftState(new ClosingState());

    context->open();
    context->close();
    context->run();
    context->stop();
}

总结与分析

状态模式的主要优点在于封装了转换规则,并枚举可能的状态,它将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为,还可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数;其缺点在于使用状态模式会增加系统类和对象的个数,且状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,对于可以切换状态的状态模式不满足“开闭原则”的要求。

参考链接