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