欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

常见设计模式之(七):状态模式

程序员文章站 2022-05-26 08:18:30
...

常见设计模式之(七):状态模式

1 什么是状态模式(state pattern)?

1.1 定义

摘自 java 设计模式官网 中 有关状态模式的定义:

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

翻译一下:
当一个对象的内部的状态改变时允许改变其行为,这个对象好像改变了它的类

1.2 状态模式中3个角色

角色 职责
AbstractState (抽象状态角色) 接口或者抽象类,负责对象状态的定义,并且封装环境角色以实现状态切换
ConcreteState (具体状态角色) 必须完成的两个职责:本状态的行为管理以及趋向状态的处理 通俗地讲:1、本状态要做的事情 2、本状态如何过渡到其他的状态
上下文角色(Context) 定义客户端需要的接口,并且负责具体状态的切换

2 简单状态模式

2.1 状态切换图

先讲一个简单的状态模式,状态之间的切换是固定的,换句话说就是:一个状态只能切换到下一个固定的状态,实例图如下:
常见设计模式之(七):状态模式

2.2 类图

通用的类图如下:
常见设计模式之(七):状态模式

2.3 c++代码

实现代码如下:

state_mode.h

#include <iostream>

class Context;
class ConcreteState1;
class ConcreteState2;
class ConcreteState3;
class AbstractState
{
public:
    AbstractState()
    {
    }

    ~AbstractState()
    {
    }
    /*
    抽象的接口,每个子类去实现这个接口
    执行自己在此任务下的逻辑任务
    */
    virtual void ExecuteTask() = 0;
    /*
    抽象的接口,每个子类去实现这个接口
    执行切换到下一个状态的逻辑实现
    */
    virtual void SwitchState(Context *context) = 0;
    //获得当前的状态
    virtual void GetState() = 0; 
};

class Context
{
public:
    //初始化状态
    Context(AbstractState * state)
    {
        m_pState = state;
    }
    ~Context()
    {

    }
    //获得当前的状态
    void GetState(void)
    {
        m_pState->GetState();
    }
    //执行状态类实现的方法
    void SwitchState(void)
    {
        //
        m_pState->SwitchState(this);
    }
    AbstractState * m_pState;
};

class ConcreteState1 : public AbstractState
{
public:
    ConcreteState1()
    {

    }
    ~ConcreteState1()
    {

    }
    virtual void ExecuteTask();
    virtual void SwitchState(Context *context);
    virtual void GetState();

};

class ConcreteState2 : public AbstractState
{
public:
    ConcreteState2()
    {

    }
    ~ConcreteState2()
    {

    }
    virtual void ExecuteTask();
    virtual void SwitchState(Context *context);
    virtual void GetState();

};

class ConcreteState3 : public AbstractState
{
public:
    ConcreteState3()
    {

    }
    ~ConcreteState3()
    {

    }
    virtual void ExecuteTask();
    virtual void SwitchState(Context *context);
    virtual void GetState();

};

state_mode.cpp

#include "state_mode.h"


void ConcreteState1::ExecuteTask() 
{       
    std::cout<<"当前状态是1,我正在执行在状态1下的任务"<< std::endl;
}
void ConcreteState1::SwitchState(Context *context) 
{
    context->m_pState = new ConcreteState2();
    std::cout<<"当前状态是1,我正在切换到状态2"<< std::endl;

}
void ConcreteState1::GetState()
{
    std::cout<<"当前状态是1"<< std::endl;
} 


void ConcreteState2::ExecuteTask() 
{       
    std::cout<<"当前状态是2,我正在执行在状态2下的任务"<<std::endl;
}
void ConcreteState2::SwitchState(Context *context) 
{
    context->m_pState = new ConcreteState3();
    std::cout<<"当前状态是2,我正在切换到状态3"<<std::endl;

}
void ConcreteState2::GetState()
{
    std::cout<<"当前状态是2"<<std::endl;
}

void ConcreteState3::ExecuteTask() 
{       
        std::cout<<"当前状态是3,我正在执行在状态3下的任务"<< std::endl;
}
void ConcreteState3::SwitchState(Context *context) 
{
    context->m_pState = (AbstractState *) new ConcreteState1;
    std::cout<<"当前状态是3,我正在切换到状态1"<< std::endl;

}
void ConcreteState3::GetState()
{
    std::cout<<"当前状态是3"<< std::endl;
}


int main(void)
{
    //初始化状态
    AbstractState *cls_state_init = new ConcreteState1();
    //建立上下文环境
    Context * cls_context = new Context(cls_state_init);
    //获取状态
    cls_context->GetState();
    //切换到下一个状态
    cls_context->SwitchState();
    //获取状态
    cls_context->GetState();
    //切换到下一个状态
    cls_context->SwitchState();
    //获取状态
    cls_context->GetState();
    //切换到下一个状态
    cls_context->SwitchState();
    //获取状态
    cls_context->GetState();
    delete cls_state_init;
    delete cls_context;
}

编译运行如下:

aaa@qq.com:/wan/07status# make state_mode 
g++     state_mode.cpp   -o state_mode
aaa@qq.com:/wan/07status# ./state_mode
当前状态是1
当前状态是1,我正在切换到状态2
当前状态是2
当前状态是2,我正在切换到状态3
当前状态是3
当前状态是3,我正在切换到状态1
当前状态是1

3 标准状态模式

3.1 状态图

在上一节我们有个假定的前提条件就是一种状态只能切换到另一个状态,而实际的情况往往是从一种状态可以切换到其他多种状态。切换示例图如下:
常见设计模式之(七):状态模式

3.2 类图

UML类图如下:
常见设计模式之(七):状态模式

3.3 c++实现

其c++实现如下:

state_mode_ex.h

#include <iostream>

class ContextEx;
class ConcreteStateEx1;
class ConcreteStateEx2;
class ConcreteStateEx3;

/*
    抽象环境中声明一个环境角色,提供各个状态类自行访问,
    并且提供所有状态的抽象行为,由各个实现类实现。
*/
class AbstractStateEx
{
public:
    AbstractStateEx()
    {
    }

    ~AbstractStateEx()
    {
    }
    /* 抽象接口 举个例子:如果在状态1下 handle1 则执行状态1下自己的逻辑任务 
                      handle2则是从状态1切换到状态2 handle3类似。。。
    */
    virtual void Handle1(void) = 0;
    virtual void Handle2(void) = 0;
    virtual void Handle3(void) = 0;
    // 获取当前的状态
    virtual void GetState(void) = 0; 
    //设置环境角色
    virtual void SetContext(ContextEx *_context);
protected:
    //定义一个环境角色, 提供子类访问
    ContextEx *context;
};


class ConcreteStateEx1 : public AbstractStateEx
{
public:
    ConcreteStateEx1()
    {

    }
    ~ConcreteStateEx1()
    {

    }

    virtual void Handle1(void);

    virtual void Handle2(void);

    virtual void Handle3(void);

    virtual void GetState(void);

};



class ConcreteStateEx2 : public AbstractStateEx
{
public:
    ConcreteStateEx2()
    {

    }
    ~ConcreteStateEx2()
    {

    }

    virtual void Handle1(void);
    virtual void Handle2(void);
    virtual void Handle3(void);
    virtual void GetState(void);

};


class ConcreteStateEx3 : public AbstractStateEx
{
public:
    ConcreteStateEx3()
    {

    }
    ~ConcreteStateEx3()
    {

    }

    virtual void Handle1(void);
    virtual void Handle2(void);
    virtual void Handle3(void);
    virtual void GetState(void);
};


class ContextEx
{
public:
    //初始化状态
    ContextEx()
    {
        m_ConState1 = new ConcreteStateEx1();
        m_ConState2 = new ConcreteStateEx2();
        m_ConState3 = new ConcreteStateEx3();
    }
    ~ContextEx()
    {
        delete m_ConState2;
        delete m_ConState3;
        delete m_ConState1;
    }
    //获得当前的状态
    void GetState(void)
    {
        m_pState->GetState();
    }

    //设置当前状态
    void SetState(AbstractStateEx  * _pState)
    {
        // 更新状态值
        m_pState = _pState;
        // 更新状态中上下文角色
        m_pState->SetContext(this);
    }
    //行为委托
    void Handle1(void)
    {
        m_pState->Handle1();
    }

    void Handle2(void)
    {
        m_pState->Handle2();
    }

    void Handle3(void)
    {
        m_pState->Handle3();
    }
    
    AbstractStateEx  * m_pState;
    ConcreteStateEx1 * m_ConState1 = NULL;
    ConcreteStateEx2 * m_ConState2 = NULL;
    ConcreteStateEx3 * m_ConState3 = NULL;
};

state_mode_ex.cpp

#include "state_mode_ex.h"

void AbstractStateEx::SetContext(ContextEx *_context)
{
    context = _context;
}


void ConcreteStateEx1::Handle1(void)
{
    std::cout << "当前处于状态1,正在执行自己的逻辑任务"<<std::endl;
}

void ConcreteStateEx1::Handle2(void)
{
    context->SetState(context->m_ConState2);
    std::cout << "当前处于状态1,正在向状态2切换"<<std::endl;
    context->Handle2();
}

void ConcreteStateEx1::Handle3(void)
{
    context->SetState(context->m_ConState3);
    std::cout << "当前处于状态1,正在向状态3切换"<<std::endl;
    context->Handle3();
}

void ConcreteStateEx1::GetState(void)
{
    std::cout << "当前处于状态1"<<std::endl;
}


void ConcreteStateEx2::Handle1(void)
{
    context->SetState(context->m_ConState1);
    std::cout << "当前处于状态2,正在向状态1切换"<<std::endl;
    context->Handle1();
}

void ConcreteStateEx2::Handle2(void)
{
    std::cout << "当前处于状态2,正在执行自己的逻辑任务"<<std::endl;
}

void ConcreteStateEx2::Handle3(void)
{
    context->SetState(context->m_ConState3);
    std::cout << "当前处于状态2,正在向状态3切换"<<std::endl;
    context->Handle3();
}

void ConcreteStateEx2::GetState(void)
{
    std::cout << "当前处于状态2"<<std::endl;
}


void ConcreteStateEx3::Handle1(void)
{
    context->SetState(context->m_ConState1);
    std::cout << "当前处于状态3,正在向状态1切换"<<std::endl;
    context->Handle1();
}

void ConcreteStateEx3::Handle2(void)
{
    context->SetState(context->m_ConState2);
    std::cout << "当前处于状态3,正在向状态2切换"<<std::endl;
    context->Handle2();
}

void ConcreteStateEx3::Handle3(void)
{
    std::cout << "当前处于状态3,正在执行自己的逻辑任务"<<std::endl;
}

void ConcreteStateEx3::GetState(void)
{
    std::cout << "当前处于状态3"<<std::endl;
}

//测试程序

int main(void)
{
    ContextEx * pcls_context = new ContextEx;
    // 设置初始状态
    pcls_context->SetState(pcls_context->m_ConState1);
    std::cout<<"初始状态1"<<std::endl;
    pcls_context->Handle1();
    // 下面开始进行状态切换,并且执行在该状态下的逻辑.
    // state1 >>> state2 
    std::cout<<"state1 >>> state2"<<std::endl;
    pcls_context->Handle2();
    pcls_context->Handle2();

    // state2 >>> state3
    std::cout<<"state2 >>> state3"<<std::endl;
    pcls_context->Handle3();
    pcls_context->Handle3();

    // state3 >>> state1
    std::cout<<"state3 >>> state1"<<std::endl;
    pcls_context->Handle1();
    pcls_context->Handle1();


    // state1 >>> state3 
    std::cout<<"state1 >>> state3"<<std::endl;
    pcls_context->Handle3();
    pcls_context->Handle3();
    
    // state3 >>> state2
    std::cout<<"state3 >>> state2"<<std::endl;
    pcls_context->Handle2();
    pcls_context->Handle2();

    // state2 >>> state1
    std::cout<<"state2 >>> state1"<<std::endl;
    pcls_context->Handle1();
    pcls_context->Handle1();


    delete pcls_context;

}

编译运行如下:

aaa@qq.com:/wan/07status# g++     state_mode_ex.cpp   -o state_mode_ex -std=c++11
aaa@qq.com:/wan/07status# ./state_mode_ex
初始状态1
当前处于状态1,正在执行自己的逻辑任务
state1 >>> state2
当前处于状态1,正在向状态2切换
当前处于状态2,正在执行自己的逻辑任务
当前处于状态2,正在执行自己的逻辑任务
state2 >>> state3
当前处于状态2,正在向状态3切换
当前处于状态3,正在执行自己的逻辑任务
当前处于状态3,正在执行自己的逻辑任务
state3 >>> state1
当前处于状态3,正在向状态1切换
当前处于状态1,正在执行自己的逻辑任务
当前处于状态1,正在执行自己的逻辑任务
state1 >>> state3
当前处于状态1,正在向状态3切换
当前处于状态3,正在执行自己的逻辑任务
当前处于状态3,正在执行自己的逻辑任务
state3 >>> state2
当前处于状态3,正在向状态2切换
当前处于状态2,正在执行自己的逻辑任务
当前处于状态2,正在执行自己的逻辑任务
state2 >>> state1
当前处于状态2,正在向状态1切换
当前处于状态1,正在执行自己的逻辑任务
当前处于状态1,正在执行自己的逻辑任务

4 优缺点以及使用场景

4.1 状态模式的优点

  • 结构清晰
    避免了过多的switch…case或者if…else语句的使用, 避免了程序的复杂性,提高系统的可维护性。
  • 遵循设计原则
    很好地体现了开闭原则和单一职责原则, 每个状态都是一个子类, 你要增加状态就要增加子类, 你要修改状态, 你只修改一个子类就可以了。
  • 封装性非常好
    这也是状态模式的基本要求, 状态变换放置到类的内部来实现, 外部的调用不用知道类内部如何实现状态和行为的变换。

4.2 状态模式的缺点

状态模式既然有优点, 那当然有缺点了。 但只有一个缺点, 子类会太多, 也就是类膨胀。 如果一个事物有很多个状态也不稀奇, 如果完全使用状态模式就会有太多的子类, 不好管理, 这个需要大家在项目中自己衡量。 其实有很多方式可以解决这个状态问题, 如在数据库中建立一个状态表, 然后根据状态执行相应的操作, 这个也不复杂, 看大家的习惯和嗜好了。

4.3 状态模式的使用场景

  • 行为随状态改变而改变的场景
    这也是状态模式的根本出发点, 例如权限设计, 人员的状态不同即使执行相同的行为结果也会不同, 在这种情况下需要考虑使用状态模式。
  • 条件、 分支判断语句的替代者
    在程序中大量使用switch语句或者if判断语句会导致程序结构不清晰, 逻辑混乱, 使用状态模式可以很好地避免这一问题, 它通过扩展子类实现了条件的判断处理。

4.4 状态模式的注意事项

状态模式适用于当某个对象在它的状态发生改变时, 它的行为也随着发生比较大的变化, 也就是说在行为受状态约束的情况下可以使用状态模式, 而且使用时对象的状态最好不要超过5个。

5 实际项目中的状态模式

  • TCP中连接状态
    在tcp/ip中 tcp的连接状态就是在某几个特定的状态之前进行切换。
  • 铱星通信过程
    可以在铱星通信过程中使用状态模式
  • 操作系统中进程或者线程的状态

实际的开发中还有许多类似的情况,我们可以根据自己实际项目的需要进行选择适合自己情况的设计模式。