代码之家  ›  专栏  ›  技术社区  ›  Praetorian

多状态机的C++状态设计模式

  •  2
  • Praetorian  · 技术社区  · 14 年前

    class Context {
    public:
      /* The Context class' user calls process1() to get it to perform an action */
      void process1();
    
    private:
      class IState;
    
      void switchState( IState *newState );
    
      class IState {
        virtual void doProcess( Context &context ) = 0;
      };
    
      class StateA : public Context::IState {
        void doProcess( Context &context );
      };
      friend class StateA;
    
      class StateB : public Context::IState {
        void doProcess( Context &context );
      };
      friend class StateB;
      .
      .
      .
      class StateJ : public Context::IState {
        void doProcess( Context &context );
      };
      friend class StateJ;
    };
    

    目前,状态机的一个成功迭代运行于 Context::StateA Context::StateJ 什么时候 Context::process1()

    StateA
    StateB
    StateC
    StateD
    StateE
    StateC
    StateD
    StateE
    StateF
    StateG
    StateH
    StateI
    StateJ
    

    确定下一个状态的内部逻辑当前由相应的状态本身通过在上下文对象中存储数据来实现。我现在需要做的是添加一个 Context::process2() 在国家的执行顺序上有很大不同的选择。当然,这可以使用在context对象中设置的标志来实现,但是我想知道是否有更好的方法来实现这一点;甚至可以使用此方法重写

    这个 Visitor design pattern process1() 可以包含状态执行顺序的所有逻辑,然后按该顺序调用每个状态。同样地, process2() 会处理它自己的逻辑。

    编辑:
    对于那些回答说我应该创建一个单独的状态机的人,我之所以要避免这样做,是因为第二个状态机使用的状态代码与第一个状态机相同;只是进程不同。

    第二个状态机将经历以下状态转换:

    StateA
    StateB
    StateC
    StateJ
    

    所以我想消除重复的代码。

    4 回复  |  直到 12 年前
        1
  •  3
  •   Owen S.    14 年前

    我想在你的代码里, switchState 当需要转换到其他状态时,由各个状态调用。像这样:

    void StateA::doProcess(Context& context) {
       context.switchState(new StateB()); // NOTE: potential leak!
    }
    

    如果是这样的话,你可以考虑的一件事就是让美国回归 过渡对象 抽象地表示状态图中的控制点。然后让上下文运行一个循环,该循环执行状态,检索结果转换,并将转换映射到任何进程的适当下一个状态。可以为每个对象设置不同的过渡贴图 process 你的方法。

    • 各州不必互相了解。转变是他们向世界宣示他们所做的一切。以适当的方式在州与州之间传递信息取决于上下文。例如,这可以使状态跨不同的上下文对象重用。

    欺骗:

    • 添加一个额外的设置层。转换映射代码主要是样板文件。生成样板文件的工具可能会有所帮助。

    编辑 :样本代码在 http://pastebin.com/eBauP060 .

        2
  •  3
  •   Tomek Szpakowicz    14 年前

    您可能希望使用一种或多种技术的组合来减少重复。例如:

    1. 或者更好,创造第三类 X )不知道怎么称呼。也许是司机?有什么建议吗?或者这个类应该被称为Context,Context应该被称为SomeStateMachine?),它将包含所有公共数据和改变它的方法。

      doProcess 方法您只能:

      • 检查输入

      • 调用正确的方法

      • 更改状态,可能基于上一步的返回值。

      这样,您的状态机将只负责调用正确的方法并切换到正确的状态。其余的都在别的地方。

    2. 而不是像这样切换状态:

      context.switchState(new StateB());
      

      使你的状态成为无状态——没有实例字段,只有方法。然后在上下文中创建一组静态实例,每个IState子类一个实例。这样可以避免分配所有这些对象:

      context.switchState(&Context::StateBInstance);
      

      现在更好了。可以在包含IState实例的上下文中创建字段。哪个实例将在状态机构造期间决定。这可以让你在运行时连接你的状态机。

      void Context::Context()
      {
        theStateThatComesAfterA = &StateBInstance;
        // [...]
      }
      
      void AnotherContext::AnotherContext()
      {
        theStateThatComesAfterA = &StateCInstance;
        // [...]
      }
      
      void StateA::doProcess(Context& context)
      {
        context.DoSomething();
        // [...]
        context.switchState(context.theStateThatComesAfterA);
      }
      

    最后一个音符。别在这件事上得意忘形。 记得。有时代码中的相似性表示应该消除重复。 然后,在设计过程中,当这些概念开始分化时,由于不相关事物的强耦合,很难改变代码。

    一般来说,这是设计师的要求。您需要查看您的特定需求和约束,并选择正确的技术。这就是为什么这种状态机设计模式(以及其他许多模式)是一种设计模式,而不是库类。

        3
  •  1
  •   jyoungdev Thilo    14 年前

    正如tomekszpakowicz所指出的,如果在状态之间有不同的转换,那么就有不同的状态机。你提到的每一个“进程”在我看来都像是一个不同的状态机。

    如果您指定状态之外的转换,那么状态本身可以从一台机器重用到另一台机器;您只需要为每个状态指定不同的转换。您可能会发现您必须动态地构建状态机——也就是说,在编译时,您有一系列可供机器使用的状态和转换。在机器的构建例程中,可以通过创建状态和转换的实例将所有这些连接在一起(这一切都有点难,我想

        4
  •  0
  •   Jon Cage    14 年前

    此代码段未编译。

    private:
        class IState;
        void switchState( IState *newState );
    
        class IState {
            virtual void doProcess( Context &context ) = 0;
        };
    

    class Context
    {
    public:
        class IState;
        void switchState( IState *newState );
    
        class IState {
            virtual void doProcess( Context &context ) = 0;
        };
    
        template<typename T>
        class StateA : public Context::IState {
        public:
            void doProcess( Context &context );
        };
    
        template<typename T>
        class StateB : public Context::IState {
        public:
            void doProcess( Context &context );
        };
    
        template<typename T>
        class StateJ : public Context::IState {
        public:    
            void doProcess( Context &context );
        };
    
    public:
        /* The Context class' user calls process1() to get it to perform an action */
        template<typename T>
        void process()
        {
            StateA<T> a;
            a.doProcess(*this);
        }
    };
    
    struct Process1
    {
    };
    
    struct Process2
    {
    };
    
    void main(int, char **)
    {
        Context c;
        c.process<Process1>();
        c.process<Process2>();
    }
    

    此外,如果保持相同的状态数,就可以得到一个通用状态机。