25 July 2005
Implementing the State Pattern in C#
Implementing behavior that adapts to changes to the internal state of an object is a difficult task. If not designed correctly, such behavior can contain duplicate/nested conditional logic that is difficult to understand and maintain.
Overview of the State PatternThe state pattern encapsulates state-based behavior in discrete classes. Responsibilities are identified and delegated to appropriate states, whilst polymorphism eliminates conditional logic, and delegation removes duplication.
Such factoring simplifies logic and ultimately results in code that is easier to maintain and extend over time.
Applying the State PatternApplying the state pattern entails:
- Defining the state machine, including the states and transitions;
- Defining an abstract base class that includes default implementations for all properties and behavior handled by the state machine;
- Defining concrete classes for each state which derive from the abstract base class;
- Define properties and methods for each concrete class which override default implementations from the base class;
- Delegating to the state machine from the enclosing class.
Consider the following application of the state pattern.
An Example, the Order ClassAn Order can be processed (either successfully or unsuccessfully) or cancelled.
Defining the State MachineThe states (including the initial and final states), and the transitions with respect to the Order class are:
States:
- New, Processed, Cancelled.
Initial State:
Final States:
- The Processed and Cancelled states.
Transitions:
- Process() = Successful, between the New and Processed states;
- Process() = Unsuccessful, both starting and finishing at the New state;
- Cancel() between the New and Cancelled states.
The complete Order state machine is as follows:
Defining the Abstract Base Class
The abstract base class of the state machine is defined as follows:
- Methods corresponding to each of the state transitions are required. These methods will do nothing by default, and will be overridden by subclasses as appropriate. Each method will accept a reference to an object of the enclosing class as a parameter (the Order class in this example) which they can manipulate if required;
- For each state, an associated querying property is required, the default implementation of which will return false;
- The abstract base class, and derived classes, should be marked as internal as they are only intended to be accessed by the enclosing class.
The abstract base class for the Order example will be called OrderState, the implementation of which is as follows:
internal abstract class OrderState
{
#region Public Properties
public virtual bool IsNew
{
get {
return false; }
}
public virtual bool IsProcessed
{
get {
return false; }
}
public virtual bool IsCancelled
{
get {
return false; }
}
#endregion #region Public Methods
public virtual void Process(Order order)
{
}
public virtual void Cancel(Order order)
{
}
#endregion }
Defining the Concrete SubclassesConcrete subclasses for each of the states are defined as follows:
- Each subclass is implemented as a singleton, and does not maintain state for thread-safety reasons. See Implementing the Singleton Pattern in C# for an in-depth discussion of thread-safe singleton implementations.
- Each subclass overrides the appropriate querying property to return true;
- Each subclass may override the state transition methods to perform desired behavior. These implementations will also reset the current state of the object of the enclosing class (which is the main reason the object reference is provided).
The concrete subclasses for the Order example are called OrderNewState, OrderProcessedState and OrderCancelledState, the implementation of which are as follows:
internal class OrderNewState : OrderState
{
#region Private Fields
private static readonly OrderNewState _instance =
new OrderNewState();
#endregion #region Constructor
private OrderNewState()
{
}
#endregion #region Public Static Properties
public static OrderNewState Instance
{
get {
return _instance; }
}
#endregion #region Public Properties
public override bool IsNew
{
get {
return true; }
}
#endregion #region Public Methods
public override void Process(Order order)
{
bool success =
false;
// Do Processing... if (success)
{
order.State = OrderProcessedState.Instance;
}
base.Process(order);
}
public override void Cancel(Order order)
{
order.State = OrderCancelledState.Instance;
base.Cancel(order);
}
#endregion }
internal class OrderProcessedState : OrderState
{
#region Private Fields
private static readonly OrderProcessedState _instance =
new OrderProcessedState();
#endregion #region Constructor
private OrderProcessedState()
{
}
#endregion #region Public Static Properties
public static OrderProcessedState Instance
{
get {
return _instance; }
}
#endregion #region Public Properties
public new bool IsProcessed
{
get {
return true; }
}
#endregion }
internal class OrderCancelledState : OrderState
{
#region Private Fields
private static readonly OrderCancelledState _instance =
new OrderCancelledState();
#endregion #region Constructor
private OrderCancelledState()
{
}
#endregion #region Public Static Properties
public static OrderCancelledState Instance
{
get {
return _instance; }
}
#endregion #region Public Properties
public override bool IsCancelled
{
get {
return true; }
}
#endregion }
Delegating to the State Machine from the Enclosing ClassThe enclosing class with which the state machine is associated is defined as follows:
- A private field of the same type as the abstract base class must be defined;
- An initialisation method (typically the constructor) must set the private field to the initial state;
- The state transition methods and querying properties previously defined for the abstract base class must be defined, which delegate to the private field. The implementation of the state transition methods must pass along a reference to the enclosing class (i.e. the Order);
- An internal property state setter must be defined so that the concrete state subclasses can reset the state of the enclosing class as appropriate.
The Order class is as follows:
public class Order
{
#region Private Fields
private OrderState _state;
#endregion #region Constructors
public Order()
{
_state = OrderNewState.Instance;
}
#endregion #region Public Properties
public bool IsNew
{
get {
return _state.IsNew; }
}
public bool IsProcessed
{
get {
return _state.IsProcessed; }
}
public bool IsCancelled
{
get {
return _state.IsCancelled; }
}
#endregion #region Internal Properties
internal OrderState State
{
set { _state = value; }
}
#endregion #region Public Methods
public void Process()
{
_state.Process(
this);
}
public void Cancel()
{
_state.Cancel(
this);
}
#endregion }
The Complete Order Class Model
When to Use the State Pattern
The state pattern can be used when there is significant state-based behaviour associated with a class.
In other cases, continue to use conditional expressions (typically, with the aid of enumerations).
Further UsesThe above techniques are also applicable to implementation of the Strategy pattern. Implementation of the State and Strategy patterns is exactly the same, however, the intent of each pattern is different. The State pattern is used to model finite state machines, whilst the Strategy pattern is used to implement pluggable algorithms. Note that implementation of the Strategy pattern may not require querying properties.
Source Code for ExamplesA Visual Studio .NET 2003 solution containing the above example code can be downloaded here.