Introduction
This is an idea of implementing Finite State Machine with sub-state. Suppose there is a State Model as above picture. Below is transition table:
<o:p>
| Source state<o:p>
| Trigger<o:p>
| Target state<o:p>
| Comment<o:p>
|
1<o:p>
| No State<o:p>
| Start<o:p>
| (Init)<o:p>
| Will go to any <st1:place w:st="on"><st1:placetype w:st="on">sub-state
of <st1:placename w:st="on">Init (depends on
configuration)<o:p>
|
2<o:p>
| (Init)<o:p>
| Enter to (Init)<o:p>
| Init A<o:p>
| In case configuration is go to Init A<o:p>
|
3<o:p>
| (Init)<o:p>
| Enter to (Init)<o:p>
| Init B<o:p>
| In case configuration is go to Init B<o:p>
|
4<o:p>
| Init A<o:p>
| Load A<o:p>
| Loaded A<o:p>
| Go directly to Loaded A (is <st1:place w:st="on"><st1:placetype w:st="on">sub-state of <st1:placename w:st="on">Loaded)<o:p>
|
5<o:p>
| Init B<o:p>
| Load B<o:p>
| Loaded B<o:p>
| Go directly to Loaded B (is <st1:place w:st="on"><st1:placetype w:st="on">sub-state of <st1:placename w:st="on">Loaded)<o:p>
|
6<o:p>
| (Loaded)<o:p>
| Run<o:p>
| Processing<o:p>
| From any <st1:place w:st="on"><st1:placetype w:st="on">sub-state
of <st1:placename w:st="on">Loaded go to
Processing<o:p>
|
7<o:p>
| (Executing)<o:p>
| Pause<o:p>
| Paused<o:p>
| From any <st1:place w:st="on"><st1:placetype w:st="on">sub-state
of <st1:placename w:st="on">Executing go to
Paused<o:p>
|
8<o:p>
| Paused<o:p>
| Resume<o:p>
| (Executing)<o:p>
| Will go to sub-state which was activated before leaving
state Executing<o:p>
|
9<o:p>
| (Executing)<o:p>
| Enter to (Executing)<o:p>
| Loaded<o:p>
| In case Loaded was activated before leaving Executing<o:p>
|
10<o:p>
| (Executing)<o:p>
| Enter to (Executing)<o:p>
| Processing<o:p>
| In case Processing was activated before leaving Executing<o:p>
|
11<o:p>
| Processing<o:p>
| Stop<o:p>
| No State<o:p>
| <o:p>
|
Framework classes
Two abstract classes StateMgmt and State will be used to implement any particular state model
StateMgmt:<o:p>
This class handles the entire state model. It handles
triggers and changes current state according to received trigger.
This class contains only one state (called Root state). Root
state contains sub-states and StateMgmt will use Root state to handle triggers.
State:<o:p>
This class represents one particular state in model (can be
container-state or smallest sub-state). It can contains many sub-state, and at
one time it can have only one sub-state is activated.
If one state has sub-states, it can activate the route to
any state which is its child (can be sub-state, or sub-state of sub-state…) by
using method GoToState.
Example (see below picture):If state [C] is activated: the route from [Root] to
[C] is: [Root] --> [A] --> [C]
By using method GoToState, state [Root] can
activate its child state [F] directly, and the route will be: [Root] --> [B] --> [F]
Each state must have a pointer to StateMgmt instance, so it
can change from current state to any state in model (by using method GoToState
of class StateMgmt).
As default, when a state handles a trigger, if it has
sub-states it will call method HandleTrigger of its direct activated sub-state.
Therefore, which state handles actual triggers, it has to override method HandleTrigger.
Implementation of class StateMgmt:
public abstract class StateMgmt
{
protected State rootState = null;
public State GetCurrentState()
{
if (this.rootState != null)
{
return rootState.GetCurrentState();
}
return null;
}
public virtual void GoToState(int stateId)
{
if (rootState != null)
{
rootState.GoToState(stateId);
}
else
{
throw new Exception("Root state is not initialized");
}
}
public virtual void HandleTrigger(int trigger)
{
if (this.rootState != null)
{
rootState.HandleTrigger(trigger);
}
}
}
Implementation of class State:
public abstract class State
{
private int stateId = -1;
protected State currentSubState = null;
protected StateMgmt mgmt = null;
protected ArrayList subStates = new ArrayList();
public int StateId
{
get
{
return this.stateId;
}
set
{
this.stateId = value;
}
}
public State(int stateId, StateMgmt mgmt)
{
this.stateId = stateId;
this.mgmt = mgmt;
}
public abstract void Activated();
public abstract string GetStateName();
public State GetCurrentState()
{
if (this.currentSubState == null)
{
return this;
}
return this.currentSubState.GetCurrentState();
}
public virtual void GoToState(int stateId)
{
foreach (State s in subStates)
{
if (s.StateId == stateId)
{
this.currentSubState = s;
s.Activated();
return;
}
else
{
try
{
s.GoToState(stateId);
this.currentSubState = s;
return;
}
catch
{
}
}
}
throw new Exception("Invalid target state");
}
public virtual void HandleTrigger(int trigger)
{
if (this.currentSubState != null)
{
this.currentSubState.HandleTrigger(trigger);
}
}
public void AddSubState(State newState)
{
this.subStates.Add(newState);
}
}
Implementing an entire State model
Create classes inherit from two Framework classes:
In class DemoStateMgmt:
- Define some necessary constants (State Id and Trigger Id)
- Build up the structure of model by adding sub-states to root state of DemoStateMgmt
public class DemoStateMgmt : StateMgmt
{
public const int State_NoState = 1;
public const int State_Init = 2;
public const int State_InitA = 3;
public const int State_InitB = 4;
public const int State_Executing = 5;
public const int State_Loaded = 6;
public const int State_LoadedA = 7;
public const int State_LoadedB = 8;
public const int State_Processing = 9;
public const int State_Paused = 10;
public const int Trigger_Start = 1;
public const int Trigger_LoadA = 2;
public const int Trigger_LoadB = 3;
public const int Trigger_Run = 4;
public const int Trigger_Pause = 5;
public const int Trigger_Resume = 6;
public const int Trigger_Stop = 7;
public DemoStateMgmt()
{
this.rootState = new RootState(-1, this);
Init stateInit = new Init(State_Init, this);
stateInit.AddSubState(new InitA(State_InitA, this));
stateInit.AddSubState(new InitB(State_InitB, this));
Loaded stateLoaded = new Loaded(State_Loaded, this);
stateLoaded.AddSubState(new LoadedA(State_LoadedA, this));
stateLoaded.AddSubState(new LoadedB(State_LoadedB, this));
Executing stateExecuting = new Executing(State_Executing, this);
stateExecuting.AddSubState(stateLoaded);
stateExecuting.AddSubState(new Processing(State_Processing, this));
this.rootState.AddSubState(new NoState(State_NoState, this));
this.rootState.AddSubState(stateInit);
this.rootState.AddSubState(stateExecuting);
this.rootState.AddSubState(new Paused(State_Paused, this));
this.rootState.GoToState(State_NoState);
}
}
Normally, each smallest sub-state will override method HandleTrigger. But in some cases when the model leaves out from a state which has sub-states, only this state needs to override method HandleTrigger.
Similar idea, when the model goes to a state which has sub-states, it only needs to go to this state. It is not necessary to indicate exactly which sub-sate it will go to. But in this case, it is very important to override method Activated of this state. This method will activate (go to) exactly its sub-state as desired.
Example with class Init:
public class Init : State
{
public Init(int stateId, StateMgmt mgmt)
: base(stateId, mgmt)
{
}
public override void Activated()
{
switch (Configuration.DefaultInit)
{
case Configuration.InitA:
GoToState(DemoStateMgmt.State_InitA);
return;
case Configuration.InitB:
GoToState(DemoStateMgmt.State_InitB);
return;
}
}
public override string GetStateName()
{
return "Init";
}
}
Example with class Executing:
public class Executing : State
{
public Executing(int stateId, StateMgmt mgmt)
: base(stateId, mgmt)
{
}
public override void Activated()
{
this.currentSubState.Activated();
}
public override string GetStateName()
{
return "Executing";
}
public override void HandleTrigger(int trigger)
{
switch (trigger)
{
case DemoStateMgmt.Trigger_Pause:
Pause();
return;
}
base.HandleTrigger(trigger);
}
private void Pause()
{
this.mgmt.GoToState(DemoStateMgmt.State_Paused);
}
}
Above is explanation about the pattern and there is only source code of some classes. You can download the entire project. If you are not clear about any, or if you have any idea for this pattern, please feel free to contact with me by email: caohuuloc@gmail.com
History
2008-10-05: Version 1.0.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.