State machine implementation
Looking any bug track system you have to solve a basic problem: state machine. The state machine consider not only the state of the object but also how it should behave depending on its state. Consider the following states and behaviors:
State Next state
New Validated, Resolved
Validated Resolved
Resolved Closed
Closed Reopened
Reopened Validated, Resolved
The simplest way of changing the state is to have an enum containing the states and a ChangeState which check if the new state is valid or not:
public class Bug
{
public enum BugState
{
New,
Validated,
Resolved,
Closed,
Reopened
}
private BugState _state = BugState.New;
public string State
{
get
{
return _state.ToString();
}
}
public void ChangeState(BugState newState)
{
switch (_state)
{
case BugState.New:
if (newState == BugState.Validated || newState == BugState.Resolved)
_state = newState;
break;
case BugState.Validated:
if (newState == BugState.Resolved)
_state = newState;
break;
case BugState.Resolved:
if (newState == BugState.Closed)
_state = newState;
break;
case BugState.Closed:
if (newState == BugState.Reopened)
_state = newState;
break;
case BugState.Reopened:
if (newState == BugState.Validated || newState == BugState.Resolved)
_state = newState;
break;
default: // no state to change
break;
}
}
}
The above code is quite simple for two main reasons: there are few states and few behaviors. But the code can grow incredibly and become unmaintainable during the time. One of the possible solutions is to apply the State pattern.
The context class (Bug) contains the information of the current state through an instance of the state object (BugState)
public abstract class BugState
{
protected Bug2 _bug;
protected string _stateName;
public BugState(Bug2 bug)
{
_bug = bug;
}
public string State
{
get
{
return _stateName;
}
}
public abstract void ChangeState(BugState newState);
}
The constructor of the BugState requires the context (Bug class) which will be used by BugState subclasses in order to communicate state changes. An example of the five subclasses will be:
public class NewState : BugState
{
public NewState(Bug2 bug) : base(bug)
{
_stateName = "New";
}
// Validate the behavior
public override void ChangeState(BugState newState)
{
if(newState is ValidatedState || newState is ResolvedState)
_bug.SetState(newState);
}
}
Then the context class simple register the change from the state classes
public class Bug2
{
public readonly BugState New;
public readonly BugState Validated;
public readonly BugState Resolved;
//...
private BugState _state;
public Bug2()
{
New = new NewState(this);
Validated = new ValidatedState(this);
Resolved = new ResolvedState(this);
//...
_state = new NewState(this);
}
public string State
{
get
{
return _state.State;
}
}
public void ChangeState(BugState newState)
{
_state.ChangeState(newState);
}
internal void SetState(BugState state)
{
_state = state;
}
}
The pattern could be perfectioned in two points:
1. The states registered into the context could be pluggable
2. There should be a notification mechanism informing that the state has been changed.