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

时间:2012-04-27 12:46:47

标签: c++ design-patterns types polymorphism

我有一个状态机,有一个状态,它将一些消息(例如文本)发送到外部接收器。在转换到此状态之前(让我们称之为 Dispatching )之前的状态需要某处存储该消息,因此 Dispatching 可以在以后获取它。当消息在一个上下文中创建并在另一个上下文中使用时,它将在堆上创建, State Manager 对象(管理状态,转换和事件循环)保持对它的引用/指针。当状态机通过状态转换时,状态对象被创建和销毁。每个州都继承抽象基类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 的想法 - 从一个状态传递到下一个状态的一条信息。它存储在动态内存中,State Manager保留对它的引用,以便每个状态都可以访问它。由于可能将不同类型的数据传递到不同的状态,因此 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 )但我不喜欢这个解决方案。

另一个想法是创建下一个状态的实例并将其数据传递到其构造函数中,但这种方法会污染状态机设计,因为在先前的状态被销毁之前将创建一个状态。

1 个答案:

答案 0 :(得分:1)

创建基类的enum部分或在基类中提供纯virtual函数以返回此enum。这样,州政府就会宣传其类型。