目录
1.概述
状态模式( State Pattern)也称为状态机模式( State Machine pattern), 是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类, 属于行为型模式。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
所谓状态机,就是当一个对象状态转换的条件表达式过于复杂的时候,把状态的判断逻辑转换到不同状态的一系列类当中去。这样解释可能有点抽象,我们举一个简单的例子,我们以电梯为例,电梯可以分成开门,关门,上升/下落,停止这五个部分。首先我们要明确两点,就是首先这五种状态在同一时间只能出现一个,其次,这五种状态在满足某种条件后是可以相互转换的,比如下落到某楼层后就会进入停止状态,那么这也是状态机使用的两个前提,第一,在某段时间内只准许出现一种状态,第二,这些状态在满足某些条件后是可以相互转换的。这其实就有点类似算法中的有限状态机的形式。
2.结构
状态模式的核心是封装,状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。状态模式的UML类图如下所示:
角色定义:
抽象状态角色(State): 接口或抽象类,复杂状态定义,并且封装环境角色以实现状态切换。
具体状态角色(ConcreteState): 每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,通俗地说, 就是本状态下要做的事情,以及本状态如何过渡到其他状态。
环境角色(Context): 也称上下文,定义客户端需要的接口,并且负责具体状态的切换。
注意:编程中提到的上下文(context),可以理解为环境 或语境,每一段程序都有很多的外部变量,一旦写的一段程序中有了外部变量,这段程序就是不完整的,不能独立运行,要想让他运行,就必须把所有的外部变量的值一个一个的全部传进去,这些值的集合就叫作上下文。
3.实现
简单的流程示例代码如下:
cpp
#include <iostream>
#include <memory>
class Context;
class IState // 抽象状态接口类
{
public:
virtual ~IState() {}
virtual void handle(Context *context) = 0; // 传入上下文类接口,处理完后改变当前状态(可以理解为设置为下一状态)
};
// 具体状态接口类A
class ConcreteStateA : public IState
{
public:
void handle(Context *context) override{ // 要在类Context声明后面定义,否则会提示使用了未定义"Context"
cout << "ConcreteStateA::handle()" << endl; // 添加处理这个状态的逻辑功能代码
context->changeState(std::make_shared<ConcreteStateB>());
}
};
// 具体状态接口类B
class ConcreteStateB : public IState
{
public:
void handle(Context *context) override{
cout << "ConcreteStateB::handle()" << endl; // 添加处理这个状态的逻辑功能代码
context->changeState(std::make_shared<ConcreteStateC>());
}
};
// 具体状态接口类C
class ConcreteStateC : public IState
{
public:
void handle(Context *context) override{
cout << "ConcreteStateC::handle()" << endl; // 添加处理这个状态的逻辑功能代码
context->changeState(std::make_shared<ConcreteStateA>());
}
};
//上下文类
class Context
{
std::shared_ptr<IState> m_pState;
public:
explicit Context(std::shared_ptr<IState> pState) :m_pState(pState) {}
~Context() { }
void request() { m_pState->handle(this); } // 委托处理函数
void changeState(std::shared_ptr<IState> pState) { // 改变状态
this->m_pState= pState;
}
};
int main(){
// 初始化Context对象,Context内部实现具体状态ConcreteState内存管理,不需要手动释放
std::unique_ptr<Context> pContext(new Context(std::make_shared<ConcreteStateA>()));
context->request(); // 状态请求
context->request();
context->request();
context->request();
return 0;
}
上述实例展示的是:A->B->C->A 状态转移过程,这是由3个状态组成的环状转移图,非常好懂,也非常简单,用他展示状态模式非常形象。一些简单的好理解的状态图,非常适合状态模式去"解耦"。
4.总结
优点
-
清晰的结构和逻辑:状态机模式使得对象的行为与其状态紧密相关,使得代码结构更加清晰,逻辑更加明确。每个状态及其转换都被明确地定义和封装,使得程序易于理解和维护。
-
扩展性好:当需要添加新的状态或修改现有状态的行为时,只需要添加新的状态类或者修改现有状态类的实现,而不需要修改上下文或其他状态类的代码。这降低了代码的耦合度,提高了系统的可扩展性。
-
减少条件分支:状态机模式通过将状态转换逻辑封装在状态类中,减少了在上下文中使用大量的条件分支(如if-else或switch-case)的情况。这有助于减少代码的复杂性,提高可读性。
-
适用于复杂逻辑:对于具有复杂状态转换逻辑的系统,状态机模式能够提供一个清晰的框架来组织和管理这些逻辑,使得系统更加健壮和可靠。
缺点
-
可能增加类的数量:每个状态都需要一个单独的状态类,这可能会导致类的数量增加,从而增加系统的复杂性。然而,这可以通过合理的包结构和命名约定来管理。
-
可能引发状态泄漏:如果在状态转换过程中没有正确地管理状态对象的生命周期,可能会导致内存泄漏。因此,在实现状态机模式时,需要特别注意状态对象的创建和销毁。
-
可能增加开发和调试难度:对于不熟悉状态机模式的开发人员来说,理解和实现状态机可能会增加开发和调试的难度。然而,通过提供清晰的文档和示例代码,可以降低这种难度。
-
性能考虑:在某些情况下,频繁的状态转换可能会导致性能下降,特别是在处理大量事件或需要快速响应的场景中。然而,通过优化状态转换逻辑和使用合适的数据结构,可以减轻这种性能影响。
综上所述,状态机模式具有清晰的结构和逻辑、良好的扩展性等优点,但也可能带来类数量增加、状态泄漏等缺点。在选择是否使用状态机模式时,需要根据具体的应用场景和需求进行权衡。