In this article I will show C++ implementation of the same model, by using State Pattern. All state classes are derived from an abstract base class CState and each of them implements public method HandleEvent(EVENT evt). CSMManager class is a State Machine Manager and represents the core of our State Machine: it receives events and dispatches them to the current state for handling. Its private member m_pCurrState is a pointer to the CState base class but it always points to actual state objects. State transition is implemented as its reassignment to a different state object (object's address). This happens when some event occurs. Behaviour of this model is event-driven and, in contrast to C implementations, state transition control is not centralized here - it is not the State Manager who takes care of which state is going to be the next one. Current state decides on that itself, depending on its current conditions and events it receives.
Events.h:
States.h:
State.h:
StateTurnedOff.h:
StateTurnedOff.cpp:
StateIdle.h:
StateIdle.cpp:
StateMoving.h:
StateMoving.cpp:
StateInvalid.h:
StateInvalid.cpp:
SMManager.h:
SMManager.cpp:
main.cpp:
Output:
CStateTurnedOff::OnEntry()
Event received: EVENT_IGNITE
Whoooa! I'm turned on!
CStateTurnedOff::OnExit()
CStateIdle::OnEntry()
Event received: EVENT_ACCELERATE
Yipee! I'm accelerating!
CStateIdle::OnExit()
CStateMoving::OnEntry()
Event received: EVENT_BRAKE
Whoops! Was I too fast?
CStateMoving::OnExit()
CStateIdle::OnEntry()
Event received: EVENT_TURN_OFF
That was probably enough...
CStateIdle::OnExit()
CStateTurnedOff::OnEntry()
CStateTurnedOff::OnExit()
Note that State Manager's member which refers to the current state (m_pCurrState) is not of reference type (CState&) but a pointer (CState*). This is one of the cases where we MUST use pointer instead of reference because we want to have a variable which reffers to different objects throughout the execution and as re-seating the reference is not allowed, the only option is using a pointer. Please refer Parashift's FAQ on References and this SO question.
The reason for introducing m_prevStateID is that sometimes state machine (or some of its states) needs to know what was its previous state. Variable which keeps track of the previous state should not be of type reference (m_prevState : CState&) or pointer (m_pPrevState : CState*) as current state should not be able to access (members) of other states. It is therefore enough if it holds only the ID of the previous state.
Note that base abstract class CState declares OnEntry() and OnExit() methods - those which are executed when entering and leaving state. Current state executes HandleEvent(EVENT evt) each time it receives some event.
3 comments:
Nice post. Thanks
Nice post. Thanks
Among many solutions found on the web using state pattern, this is the most balanced approach so far. Did you also managed to make a unit test for this? I would think of something like log file comparison or something like that. Well done!
Post a Comment