State Machine Inheritance
We have already seen how the traditional state machine can be transformed into a hierarchical state machine by defining a base state and inheriting from it. In this article, we will go a step further and explore ways of inheriting from the state machine itself.
What is State Machine Inheritance?
State Machine inheritance provides a powerful way of teaching new tricks to an old state machine. Consider a IncomingCall and OutgoingCall objects in a call processing implementation. A lot of functionality between the two call types is shared. This common functionality could be implemented in a common Call object. IncomingCall and OutgoingCall can inherit from the Call object. The inheritance tree can be refined by defining call objects based on the signaling type. This would result in objects like V52IncomingCall, ISUPOutgoingCall etc. Since each of these object is a state machine, we have to consider ways by which state machine inheritance can be implemented.
Another example could be Unit state machine discussed in the hierarchical state machine article. Unit could be the base class for different types of hardware units in a system. TransducerUnit, ProcessorUnit, TerminalUnit could all inherit from Unit and extend the state machine defined by it.
We will use the Unit state machine example, to discuss the following inheritance techniques:
Overriding Methods
This technique involves overriding the methods of the base class to provide the required functionality in the deriving class. Here, the state sequencing is completely defined by the base class, the inheriting classes merely refine the methods invoked in that state machine.
The following example uses this technique. The ProcessorUnit inherits from Unit class and overrides the methods SendDiagnosticsRequest, PerformSwitchover and SendSwitchoverResponse.
State Machine Inheritance by Overriding Methods
class Unit | |
{ | |
public: | |
static Active ActiveState; | |
static Standby StandbyState; | |
static Suspect SuspectState; | |
static Failed FailedState; | |
void OnMessage(Msg *pMsg); | |
void NextState(UnitState &rState); | |
// Common Methods invoked from several states | |
virtual void SendDiagnosticsRequest(); | |
virtual void RaiseAlarm(int reason); | |
virtual void ClearAlarm(int reason); | |
virtual void PerformSwitchover(); | |
. . . | |
virtual void SendSwitchoverResponse(); | |
virtual void AbortDiagnostics(); | |
private: | |
UnitState *pCurrentState; | |
}; | |
class ProcessorUnit : public Unit | |
{ | |
public: | |
// The following methods have been overridden to | |
// support special requirements for ProcessorUnit | |
void SendDiagnosticsRequest(); | |
void PerformSwitchover(); | |
void SendSwitchoverResponse(); | |
}; | |
// Example of one of the overriden methods | |
void ProcessorUnit::SendDiagnosticsRequest() | |
{ | |
// Initialize ProcessorUnit specific fields | |
. . . | |
Unit::SendDiagnosticsRequest(); | |
} |
Overriding States
In this technique, the states defined by the base class can be overridden by inheriting class. The inheriting class provides plug in replacements for the base class states. The main objective of overriding the states is to enhance functionality of the state. The inherited states take care of specific handling for the messages in the state. Generally the state sequence defined by the base class is preserved.
To allow overriding the states of the base class, states are accessed by pointers. These pointers are initialized to state objects defined in the class. The inheriting state machine class can derive from the base class states and modify the pointer appropriately.
In the example give below, Unit state machine object has been modified to access all states through pointers. The Unit constructor initializes these pointers to the appropriate states. The deriving TerminalUnit class overrides the Active, Standby and Suspect states of the Unit class. The constructor initializes the pointers to these states to the derived states, TerminalActiveState, TerminalStandbyState and TerminalSuspectState.
State Machine Inheritance by Overriding States
class Unit | |
{ | |
public: | |
// State declarations have been changed to pointers, so that they can be overridden | |
// by the inheriting classes. (Note: These pointers can no longer be kept static, as | |
// they are not shared by all state machine objects) | |
Active *pActiveState; | |
Standby *pStandbyState; | |
Suspect *pSuspectState; | |
Failed *pFailedState; | |
// States defined by Unit state machine | |
static Active activeState; | |
static Standby standbyState; | |
static Suspect suspectState; | |
static Failed failedState; | |
Unit() | |
{ | |
// Initialize the state pointers to state definitions | |
// in Unit. These pointers can be reinitialized by inheriting | |
// states. | |
pActiveState = &activeState; | |
pStandbyState = &standbyState; | |
pSuspectState = &suspendState; | |
pFailedState = &failedState; | |
} | |
void OnMessage(Msg *pMsg); | |
// NextState now takes a pointer input | |
void NextState(UnitState *pState); | |
private: | |
UnitState *pCurrentState; | |
}; | |
// Changing the states to pointers will result in changing all | |
// references to these variables to pointers. An example of one such method is | |
// shown below | |
void Standby::OnSwitchover(Unit &u, Msg *pMsg) | |
{ | |
Inservice::OnSwitchover(u, pMsg); | |
u.NextState(u.pActiveState); // Changed to pointer | |
} | |
// The following three states have been overridden for | |
// the TerminalUnit class. Note that Failed class | |
// implementation from the base class has beeen used | |
// without any change | |
class TerminalActiveState : public Active | |
{ | |
. . . | |
}; | |
class TerminalStandbyState : public Standby | |
{ | |
. . . | |
}; | |
class TerminalSuspectState : public Suspect | |
{ | |
. . . | |
}; | |
class TerminalUnit : public Unit | |
{ | |
static TerminalActiveState termActiveState; | |
static TerminalStandbyState termStandyState; | |
static TerminalSuspectState termSuspectState; | |
public: | |
TerminalUnit() | |
{ | |
// TerminalUnit constructor initializes the overridden | |
// classes | |
pActiveState = &termActiveState; | |
pStandbyState = &termStandbyState; | |
pSuspectState = &termSuspectState; | |
} | |
}; |
Dividing Into Sub-States
In this case too, the states of the base class are overridden to enhance functionality. The difference is that one base class state may map to many derived class sub-states. The pointer is initialized to one of the derived sub-states, referred to as the triggering sub-state. The state transitions to other derived sub-states are initiated by the triggering sub-state. Even though the sub-states have been introduced in the derived class, the base class state machine sequencing is still preserved. From the base class's perspective, it transitions to one state, the inheriting class performs sub-state transitions without the knowledge of the base class.
In the example given below, the Suspect states of TransducerUnit are shown. The transducer diagnostics tests generally involve setting up the test, running the test and finally dismantling the test. Thus the suspect state needs to be broken down into three sub-state. These sub-states are TransducerTestSetupState, TransducerTestRunningState and TransducerTestDismantleState. These sub-states derive from the Suspect state of the Unit base class. The base class's Suspect state pointer is initialized to the triggering class, TransducerTestSetupState in the constructor. The transitions to other Suspect sub-states are triggered from this class. Finally, the TransducerUnit state machine transitions out to a state supported by the Unit base class.
State Machine Inheritance by Dividing Into Sub-States
class Unit | |
{ | |
public: | |
Active *pActiveState; | |
Standby *pStandbyState; | |
Suspect *pSuspectState; | |
Failed *pFailedState; | |
void OnMessage(Msg *pMsg); | |
// NextState now takes a pointer input | |
void NextState(UnitState *pState); | |
private: | |
UnitState *pCurrentState; | |
}; | |
// The following three states together override the Suspect state | |
// Suspect.TestSetup, Suspect.TestRunning and Suspect.TestDismantle | |
// are three sub-states of the Suspect state | |
class TransducerTestSetupState : public Suspect | |
{ | |
. . . | |
}; | |
class TransducerTestRunningState : public Suspect | |
{ | |
. . . | |
}; | |
class TransducerTestDismantleState : public Suspect | |
{ | |
. . . | |
}; | |
class TransducerUnit : public Unit | |
{ | |
public: | |
TransTestRunningState *pTestSetupState; | |
TransTestDismantleState *pTestDismantleState; | |
static TransducerTestSetupState transTestSetupState; | |
static TransducerTestRunningState transTestRunningState; | |
static TransducerTestDismantleState transTestDismantleState; | |
TransducerUnit() | |
{ | |
// TransducerUnit constructor initializes the overridden | |
// class's Suspect pointer to the first TransducerSuspect sub-state, | |
// i.e. TransducerTestSetupState. | |
// Transitions to other Suspect sub-states will be triggered from | |
// this sub state. The suspect sub-states will finally transition | |
// out to the other states supported by the Unit state machine. | |
pSuspectState = &transTestSetupState; | |
} | |
}; |