简体   繁体   English

如何强制耦合多态类型和枚举值?

[英]How to enforce coupling polymorphic type and enum value?

I have a state machine with one state which dispatches some message (eg text) to an external receiver. 我有一台状态机,该状态机将某种消息(例如文本)发送到外部接收器。 Before transiting to this state (let's call it Dispatching ) previous state needs somewhere to store that message so Dispatching can fetch it later. 在转换到此状态之前(将其称为Dispatching ),先前的状态需要存储该消息的位置,以便Dispatching可以稍后再获取它。 As message is created in one context and consumed in another, it will be created on the heap and State Manager object (which manages states, transitions and event loop) keeps a reference/pointer to it. 当消息在一个上下文中创建并在另一个上下文中使用时,它将在堆上创建,并且State Manager对象(用于管理状态,转换和事件循环)将保留对它的引用/指针。 State objects are created and destroyed as state machine transits through states. 状态对象在状态机穿越状态时创建和销毁。 Each state inherits abstract base class State : 每个状态都继承抽象基类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_;    
};

In order to make passing data to the next state generic, I came up with the idea of StateData - a piece of information passed from one state to the next one. 为了使将数据传递给下一个状态通用,我想到了StateData的概念-从一个状态传递到下一个状态的一条信息。 It is stored in dynamic memory, State Manager keeps a reference to it so each state can access it. 它存储在动态内存中,状态管理器保留对其的引用,以便每个状态都可以访问它。 As it is possible that different types of data will be passed to different states, StateData can be made abstract base class, specialized for each particular state: 由于可能会将不同类型的数据传递给不同的状态,因此可以将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; 
         }             
         ... 
      }
   }    
   ...          
}

There is a pitfall in following lines: 以下几行有一个陷阱:

stateMachine_.pStateData_.reset(new StateBData("Hello from StateA"));
return STATE_B;

If transition logic changes so from here we need to go to STATE_C , developer can forget to change the type of StateBData to StateCData : 如果转换逻辑发生变化,那么从此处开始,我们需要转到STATE_C ,开发人员可能会忘记将StateBData的类型StateBDataStateCData

stateMachine_.pStateData_.reset(new StateBData("Hello from StateA"));
return STATE_C;

...which would lead to undesired behaviour when StateC tries to cast down StateData to StateCData . ...这会在StateC尝试将StateData StateCDataStateCData时导致不良行为。

How to avoid this? 如何避免这种情况? How to enforce matching the type of created object and returned enum value? 如何强制匹配所创建对象的类型和返回的枚举值?

Yeah, this code stinks and this is the consequence of using two piece information and using enum to distinguish state types instead of types themselves. 是的,这段代码很臭,这是使用enum信息并使用enum来区分状态类型而不是类型本身的结果。 HandleEvent could return StateXData and depending on this returned type (as it carries the information about the next state) State Manager would determine (by using RTTI) the next state to transit to ( X ) but I don't like this solution. HandleEvent可以返回StateXData并根据此返回的类型(因为它包含有关下一个状态的信息),状态管理器将确定(通过使用RTTI)下一个要转换为( X )的状态,但是我不喜欢这种解决方案。

Another idea is to create an instance of the next state and pass its data into its constructor but this approach would pollute state machine design as one state would be created before previous state is destroyed. 另一个想法是创建下一个状态的实例并将其数据传递到其构造函数中,但是这种方法会污染状态机设计,因为在销毁前一个状态之前会创建一个状态。

Make the enum part of the base class or provide a pure virtual function in the base class to return this enum . enum作为基类的一部分,或在基类中提供纯virtual函数以返回此enum This way, the state itself will advertise its type. 这样,状态本身将通告其类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM