Pierre Greborio.NET

Talking about .NET world

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.

Comments

Paul D. Murphy said:

Microsoft Fugue out of MS Research is an amazing tool for regualting state machine usage complaince. Check it out.

Paul
# January 27, 2005 8:25 PM