0

私は現在、多層ステート マシンをコーディングするためのクリーンな設計を考え出そうとしていますが、これまでのところ、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 は未定義です。

最後に、ルート状態には、以前と同じロジックに従って、複合状態に基づいて取ることができる一連の状態があります。

ここまでは、ルートがサブステートを呼び出し、サブステートがアトミック ステートを呼び出してそれらを更新し、カスケード バックするトップダウン アプローチを試しました。

また、ボトムアップ アプローチも試みました。このアプローチでは、アトミック ステートが更新されてサブステートが呼び出され、サブステートが呼び出されてルート ステートが呼び出されます。

どちらの場合も、単一のサブステートが 1 つまたは多数のアトミック ステートに依存する可能性があるという事実により、ステートの検証が非常に複雑になり、許容できない肥大化したコードになってしまいます。別の種類のアプローチが必要な気がしますが、現在のデザインにこだわっています。この種の問題を経験したことがあり、インスピレーションを提供できる人がいれば、本当に感謝しています。

4

1 に答える 1

0

One quick thought:

1) Observer pattern between composite and atomic states ("a", "b", "c" as observees and "A" as observer, so on..)

2) Have class-A implemented using Pimpl idiom to separate the interface & implementation details, thus have more control and exhibit more flexibily in changing the implementation details.

3) Let class-A have some kind of Factory abstraction to create & manage specialized implementation-objects for each unique state of "A".

4) Thus, whenever changes on a, b, c are observed by "A", Factory helps to retrieve the corresponding implementation-state of "A" and does a state change. Apply same approach between "A" and "X".

More detailed layout:

1) Define the required interfaces (generic or abstract classes) IX, IA, Ia, Ib, Ic.

2) In interface IA, define public IaChanged(Ia*), IbChanged(Ib*) & IcChanged(Ic*) methods to receive Ia, Ib & Ic state change notifications (Callback methods of Observer pattern).

3) Inside the atomic interfaces Ia, Ib & Ic. Define public Register(IA&) & private Notify() metods. In Interface Ia,

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

In Interface Ib,

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

so on...

4) Have clases X, A, a, b, c derived from respective interfaces. X->IX, A->IA, a->Ia, b->Ib & c->Ic, where -> represents "derives".

5) Have class A define A_implState (Pimpl Idiom), where A_implState may derive from a new interface IA_implState to keep things generic.

Have classes A_implState0, A_implState1, A_implState2, A_implStateX as specialized versions of IA_implState.

where,

    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) Have one specialization of IA_Impl for each distinct states of A, based on:

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

7) In class A, whenever IaChanged(IaPtr) or IbChanged(IbPtr) or IcChanged(IcPtr) gets triggered by respective observee, process the change notifications as:

   // 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();
    }

/* a rough sketch, may look like */

use shared_pointers for managing lifetime, define some typedefs for ease of use.

    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;
    };

// Initialize Container with all possible implementation states for class-A

    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));
    }

// Determine the A's current state based on a, b & c' current state

    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; 
    }
于 2013-02-28T02:19:53.330 に答える