【问题标题】:Multi-tier complex state machine多层复杂状态机
【发布时间】:2013-02-27 23:21:54
【问题描述】:

我目前正在尝试为多层状态机的编码提出一种简洁的设计,但到目前为止,我还没有在有关 C++ 或其他语言中正常状态机使用的文章中找到解决方案。

在层次结构的最底部,我们有原子状态:a、b、c、...、x、y、z。 除此之外,还有第一层复合状态:A、B、C、D。 最后,在最顶端,有一个最终聚合根状态 X。

       X
    A B C D
a b c d e f g h...

与通常的状态机相比,最低状态是由外部因素定义和确定的;没有可检测到的事件会改变它们,只需通过观察即可检测到变化。

在原子状态发生变化后,第一个复合层具有一组可以采用的状态,具体取决于较低状态的组合。例如:a、b 和 c 是 A 的“子”状态:

a b c - A
0 0 0 - 0
1 0 0 - 1
2 0 0 - x
2 1 0 - 2

等等...其中 x 未定义。

最后,根状态有一组可以采用的状态,基于复合状态 - 遵循与之前相同的逻辑。

到目前为止,我尝试了一种自上而下的方法,在这种方法中,根会调用子状态,而子状态又会调用原子状态来更新它们,然后级联起来。

我还尝试了一种自下而上的方法,其中原子状态会更新并调用到子状态,而子状态又会调用到根状态。

在这两种情况下,单个子状态可能仅依赖于一个或多个原子状态这一事实使状态验证变得非常复杂,我最终得到了不可接受的臃肿代码。我觉得我需要一种不同的方法,但我坚持目前的设计。如果有人遇到过此类问题并能提供一些启发,我将不胜感激。

【问题讨论】:

  • 看起来很有趣,但对这个问题没有帮助,因为状态之间的转换是未知的——我们只能在状态变化发生后观察状态变化,并据此调整上层状态.
  • 你不知道从一种状态到另一种状态的转换是如何发生的??我的意思是,如果你在state a,然后发生了一些事情,然后你在state A,你已经测量了将当前状态标记为A的东西。所以,你有你已知的状态改变者......你观察到的东西。
  • state a 是 1,然后是 2 - 除了假设一个变量发生变化之外,没有什么可跟踪的。如果你愿意,a 是真实状态的包装器,除了读取那个变量外,我们无法访问它。当然,您可以任意决定从一种状态到另一种状态的每次转换都是一个事件,但是您有 X 乘以 X 个事件,其中 X 是状态的数量。这有两个问题,首先不是所有这些事件都是有效的,其次,我们不知道哪些是有效的,哪些是无效的。这必须在执行期间“学习”。
  • 但您识别状态1 和状态2,这就是您测量/观察的;那个“一个变量”。下一步:即使是“无效”状态也是一个状态/或事件:它只是一个标记为“无效”的事件/状态。在您的示例中,您可以从x 输入任何其他状态。所以,仍然:深入了解 ragel。您可以定义过渡动作等。

标签: c++ design-patterns state-machine


【解决方案1】:

快速思考:

1) 复合状态和原子状态之间的观察者模式(“a”、“b”、“c”作为被观察者,“A”作为观察者,等等..)

2) 使用 Pimpl idiom 实现 class-A 以分离接口和实现细节,从而在更改实现细节时拥有更多控制权并表现出更灵活。

3) 让 A 类具有某种工厂抽象来为“A”的每个唯一状态创建和管理专门的实现对象。

4) 因此,每当“A”观察到 a、b、c 的变化时,Factory 都会帮助检索“A”的相应实现状态并进行状态变化。 在“A”和“X”之间应用相同的方法。

更详细的布局:

1) 定义所需的接口(通用或抽象类)IX、IA、Ia、Ib、Ic。

2) 在接口 IA 中,定义 public IaChanged(Ia*)、IbChanged(Ib*) 和 IcChanged(Ic*) 方法来接收 Ia、Ib 和 Ic 状态变化通知(观察者模式的回调方法)。

3) 在原子接口 Ia、Ib 和 Ic 内部。定义公共 Register(IA&) 和私有 Notify() 方法。 在接口 Ia 中,

    Where Notify() { foreach(observer in m_Observers) 
                     observer->IaChanged(this);
                     }

在接口 Ib 中,

    Where Notify() { foreach(observer in m_Observers) 
                     observer->IbChanged(this);
                     }

等等……

4) 具有从各自接口派生的类 X、A、a、b、c。 X->IX, A->IA, a->Ia, b->Ib & c->Ic,其中->代表“衍生”。

5) 让 A 类定义 A_implState (Pimpl Idiom),其中 A_implState 可以派生自新接口 IA_implState 以保持通用性。

将类 A_implState0、A_implState1、A_implState2、A_implStateX 作为 IA_implState 的专用版本。

在哪里,

    class IA_implState
    {
        public:
           virtual void processStateChange()=0;
    };

    class A_implState0 : public IA_implState
    {
        public:
            void processStateChange()
            {
                // do your stuff specific to State "0" of "A".
            }
    };

    class A_implStateX : public IA_implState
    {
        public:
            void processStateChange()
            {
                // do your stuff specific to State "X" of "A".
            }
    };      
    so on...

6) 对 A 的每个不同状态都有一个 IA_Impl 特化,基于:

a b c - A
0 0 0 - 0
1 0 0 - 1
2 0 0 - x
2 1 0 - 2

7) 在 A 类中,每当 IaChanged(IaPtr) 或 IbChanged(IbPtr) 或 IcChanged(IcPtr) 被相应的被观察者触发时,将更改通知处理为:

   // a changed
    void A::IaChanged(IaPtr a)
    {
     //Buffer Ia inside a member
     m_pIa = a;
     //Retrieve A-implementer based on the current state.
     m_pimplA = m_implAContainer[GetCurrentState()]; // or use FactoryMethod or AbstractFactory pattern if required.
     m_pimplA->processStateChange();
    }

   // b changed
    void A::IbChanged(IbPtr b)
    {
      //Buffer Ib inside a member
      m_pIb = b;
      m_pimplA = m_implAContainer[GetCurrentState()]; // use FactoryMethod or AbstractFactory pattern if required.
      m_pimplA->processStateChange();
    }

/* 粗略的草图,可能看起来像 */

使用 shared_pointers 来管理生命周期,定义一些 typedef 以便于使用。

    typedef std::shared_ptr<Ia> IaPtr;
    typedef std::shared_ptr<Ib> IbPtr;
    typedef std::shared_ptr<IA_impl> IAImplPtr;
    typedef std::map<int /* or other datatype as required */ , IA_implPtr> ImplAPtrContainer;

    // class-A may look like
    class A : public IA
    {
     public:
      void IaChanged(const IaPtr ptr_a);
      void IbChanged(const IbPtr ptr_b);
      void Init();
      void DeInit() { m_implAContainer.clear(); }

    private:
       int GetCurrentState();

    private:
     ImplAPtrContainer m_implAContainer;
     IAImplPtr m_pimplA;
     IaPtr m_aPtr;
     IbPtr m_bPtr;
     IcPtr m_cPtr;
    };

// 使用 A 类的所有可能实现状态初始化 Container

    void A::Init()
    {
      m_implAContainer.insert(/*state*/ 0, IAImplPtr(new A_implState0));
      m_implAContainer.insert(/*state*/ 1, IAImplPtr(new A_implState1));
      m_implAContainer.insert(/*state*/ X, IAImplPtr(new A_implStateX));
    }

//根据a,b&c'当前状态判断A的当前状态

    int A::GetCurrentState()
    {
        // Have this method return A's state based on a b c, prefer enums over magic numbers
        if(m_aPtr->GetState() == 0 && m_bPtr->GetState() == 0 && m_cPtr->GetState() == 0)
          return 0; 
    }

【讨论】:

  • 嘿阿伦,我需要一段时间来消化这个,但它似乎可以工作。至少,它将帮助我采取不同的方法。如果我猜对了,你解决了 Factory 方法中状态模式的复杂性,并且拥有多个 A 版本?
  • 您好 Awishformore,如果您认为 factory 太过分了,请查看新的编辑。它用容器代替工厂。容器是一种微不足道的工厂,充当查找。如果我们要处理不可变的、确定的和小的状态集,那么它们是更可取的。希望这符合您的用例。
  • 我喜欢这种方法,但困扰我的一件事是每个状态更改都需要一个单独的方法 - Iachanged、Ibchanged 等。会有很多原子状态,所以它会最好有一种通用的方法来做到这一点——但是,同样,我们只知道改变状态的引用,而不知道是哪个状态——我想在这里使用某种 ID 就可以了。你怎么看?我还考虑了 GetCurrenState() 方法——如何将哈希图实现为查找表?这样可以快速查找大量复杂状态,对吗?
  • 是的,使用通用回调方法作为参数传递的基于 ID 的原子对象肯定会降低为每个原子状态使用单独方法的复杂性。此外,如果外部事件本质上是异步的(在大多数情况下),此方法可能必须序列化几个可能同时发生的原子更改?此外,使用散列处理 GetCurrentState() 的内部逻辑将使事情变得更简单和更快,只要您可以生成良好的散列函数。
  • 假设原子状态更改过于频繁且异步,那么最好使用某种代理来屏蔽频繁更新(除非复合状态需要)。代理将位于原子状态和直接复合状态之间,并将序列化来自原子状态的传入流量。推模型? :)
猜你喜欢
  • 2016-10-25
  • 1970-01-01
  • 1970-01-01
  • 2012-11-26
  • 2017-10-30
  • 2013-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多