我有一个状态机,它向外部接收器发送一些消息(例如文本)。在转换到这个状态(我们称之为Dispatching)之前,前一个状态需要在某个地方存储该消息,以便Dispatching可以稍后获取它。当消息在一个上下文中创建并在另一个上下文中使用时,它将在堆上创建,并且状态管理器对象(管理状态、转换和事件循环)保持一个指向它的引用/指针。状态对象在状态机通过状态时被创建和销毁。每个状态都继承抽象基类State
:
enum StateID
{
STATE_A,
STATE_B,
...
};
class State
{
public:
State(StateID stateID, StateManager& sm) :
stateID_(stateID), sm(sm_){}
virtual ~State(){};
virtual StateID HandleEvent(const Event& e) = 0;
StateID id() const {return stateID_;}
protected:
StateID stateID_;
StateManager& sm_;
};
为了使将数据传递到下一个状态通用,我提出了StateData的想法——一条从一个状态传递到下一个状态的信息。它存储在动态内存中,状态管理器保留对它的引用,以便每个状态都可以访问它。由于可能会将不同类型的数据传递给不同的状态,因此可以将StateData制作为抽象基类,专门针对每个特定状态:
struct StateData
{
virtual ~StateData() = 0;
};
struct StateAData : public StateData
{
int n_;
StateAData(int n) : n_(n){}
};
struct StateBData : public StateData
{
std::string str_;
StateBData(const std::string& str) : str_(str){}
};
...
class StateManager
{
boost::scoped_ptr<State> pCurrState_;
boost::scoped_ptr<StateData> pStateData_;
...
public:
void runEventLoop()
{
while(true)
{
...
//get event from a queue
...
StateID nextStateID = pCurrState_->HandleEvent(e);
if(nextStateID == pCurrState_->id())
continue;
pCurrState_.reset(0);
switch(nextStateID)
{
case STATE_A:
pCurrState_.reset(new StateA(*this));
break;
case STATE_B:
pCurrState_.reset(new StateB(*this));
break;
case STATE_C:
pCurrState_.reset(new StateC(*this));
break;
...
}
}
}
...
};
class StateA
{
public:
StateA(StateManager& sm) : State(STATE_A, sm){}
StateID HandleEvent(const Event& e)
{
switch(e.ID)
{
case EVENT_1:
{
StateAData* pData = reinterpret_cast<StateAData*>(stateMachine_.pStateData_.get());
// do something with data, e.g. use it for transition logic
if(pData->n_ % 2)
{
stateMachine_.pStateData_.reset(new StateBData("Hello from StateA"));
return STATE_B;
}
else
{
...
}
break;
}
...
}
}
...
}
以下几行有一个陷阱:
stateMachine_.pStateData_.reset(new StateBData("Hello from StateA"));
return STATE_B;
如果转换逻辑发生变化,因此我们需要从这里转到STATE_C
,开发人员可能会忘记将类型更改StateBData
为StateCData
:
stateMachine_.pStateData_.reset(new StateBData("Hello from StateA"));
return STATE_C;
StateC
...这会在尝试转换StateData
为.时导致不良行为StateCData
。
如何避免这种情况?如何强制匹配创建对象的类型和返回的枚举值?
是的,这段代码很臭,这是使用两部分信息并enum
用于区分状态类型而不是类型本身的结果。HandleEvent
可以返回StateXData
并根据这个返回的类型(因为它携带有关下一个状态的信息)状态管理器将确定(通过使用 RTTI)下一个状态转换到(X
),但我不喜欢这个解决方案。
另一个想法是创建下一个状态的实例并将其数据传递给它的构造函数,但这种方法会污染状态机设计,因为会在前一个状态被破坏之前创建一个状态。