Design patterns and practices in .NET: the State pattern

Introduction

The State design pattern allows to change the behaviour of a method through the state of an object. A typical scenario where the state pattern is used when an object goes through different phases. An issue in a bug tracking application can have the following states: inserted, reviewed, rejected, processing, resolved and possibly many others. Depending on the state of the bug the behaviour of the underlying system may also change: some methods will become (un)available and some of them will change their behaviour. You may have seen or even produced code similar to this:

public void ProcessBug()
{
	switch (state)
	{
		case "Inserted":
			//call another method based on the current state
			break;
		case "Reviewed":
			//call another method based on the current state
			break;
		case "Rejected":
			//call another method based on the current state
			break;
		case "Resolved":
			//call another method based on the current state
			break;
	}
}

Here we change the behaviour of the ProcessBug() method based on the state of the “state” parameter, which represents the state of the bug. You can imagine that once a bug has reached the Rejected status then it cannot be Reviewed any more. Also, once it has been reviewed, it cannot be deleted. There are other similar scenarios like that where the available actions and paths depend on the actual state of an object.

Suppose you have public methods to perform certain operations on an object: Insert, Delete, Edit, Resolve, Reject. If you follow the above solution then you will have to insert a switch statement in each and check the actual state of the object and act accordingly. This is clearly not maintainable; it’s easy to get lost in the chain of the logic, it gets difficult to update the code if the rules change and the class code grows unreasonably large compared to the amount of logic carried out.

There are other issues with the naive switch-statement approach:

  • The states are hard coded which offers no or little extensibility
  • If we introduce a new state we have to extend every single switch statement to account for it
  • All actions for a particular state are spread around the actions: a change in one state action may have an effect on the other states
  • Difficult to unit test: each method can have a switch statement creating many permutations of the inputs and the corresponding outputs

In the switch statement solution the states are relegated to simple string properties. In reality they are more likely to be more important objects that are part of the core Domain. Hence that logic should be encapsulated into separate objects that can be tested independently of the other concrete state types.

Demo

We’ll simulate an e-commerce application where an order can go through the following states: New, Shipped, Cancelled. The rules are simple: a new order can be shipped or cancelled. Shipped and cancelled orders cannot be shipped or cancelled again.

Fire up Visual Studio and create a blank solution. Insert a Windows class library called Domains. You can delete Class1.cs. The first item we’ll insert is a simple enumeration:

public enum OrderStatus
	{
		New
		, Shipped
		, Cancelled
	}

Next we’ll insert the interface that each State will need to implement, IOrderState:

public interface IOrderState
	{
		bool CanShip(Order order);
		void Ship(Order order);
		bool CanCancel(Order order);
		void Cancel(Order order);
                OrderStatus Status {get;}
	}

Each concrete state will need to handle these methods independently of the other state types. The Order domain looks like this:

public class Order
	{
		private IOrderState _orderState;

		public Order(IOrderState orderState)
		{
			_orderState = orderState;
		}

		public int Id { get; set; }
		public string Customer { get; set; }
		public DateTime OrderDate { get; set; }
		public OrderStatus Status
		{
			get
			{
				return _orderState.Status;
			}
		}
		public bool CanCancel()
		{
			return _orderState.CanCancel(this);
		}
		public void Cancel()
		{
			if (CanCancel())
				_orderState.Cancel(this);
		}
		public bool CanShip()
		{
			return _orderState.CanShip(this);
		}
		public void Ship()
		{
			if (CanShip())
				_orderState.Ship(this);
		}

		void Change(IOrderState orderState)
		{
			_orderState = orderState;
		}
	}

As you can see each Order related action is delegated to the OrderState object where the Order object is completely oblivious of the actual state. It only sees the interface, i.e. an abstraction, which facilitates loose coupling and enhanced testability.

Let’s implement the Cancelled state first:

public class CancelledState : IOrderState
	{
		public bool CanShip(Order order)
		{
			return false;
		}

		public void Ship(Order order)
		{
			throw new NotImplementedException("Cannot ship, already cancelled.");
		}

		public bool CanCancel(Order order)
		{
			return false;
		}

		public void Cancel(Order order)
		{
			throw new NotImplementedException("Already cancelled.");
		}

		public OrderStatus Status
		{
			get
			{
				return OrderStatus.Cancelled;
			}
		}
	}

This should be easy to follow: we incorporate the cancellation and shipping rules within this concrete state.

ShippedState.cs is also straighyforward:

 
public class ShippedState : IOrderState
	{
		public bool CanShip(Order order)
		{
			return false;
		}

		public void Ship(Order order)
		{
			throw new NotImplementedException("Already shipped.");
		}

		public bool CanCancel(Order order)
		{
			return false;
		}

		public void Cancel(Order order)
		{
			throw new NotImplementedException("Already shipped, cannot cancel.");
		}

		public OrderStatus Status
		{
			get { return OrderStatus.Shipped; }
		}
	}

NewState.cs is somewhat more exciting as we change the state of the order after it has been shipped or cancelled:

 
public class NewState : IOrderState
	{
		public bool CanShip(Order order)
		{
			return true;
		}

		public void Ship(Order order)
		{
			//actual shipping logic ignored, only changing the status
			order.Change(new ShippedState());
		}

		public bool CanCancel(Order order)
		{
			return true;
		}

		public void Cancel(Order order)
		{
			//actual cancellation logic ignored, only changing the status;
			order.Change(new CancelledState());
		}

		public OrderStatus Status
		{
			get { return OrderStatus.New; }
		}
	}

That’s it really, the state pattern is not more complicated than this.

We separated out the state-dependent logic to standalone classes that can be tested independently. It’s now easy to introduce new states later. We won’t have to extend dozens of switch statements – the new state object will handle that logic internally. The Order object is no longer concerned with the concrete state objects – it delegates the cancellation and shipping actions to the states.

View the list of posts on Architecture and Patterns here.

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

12 Responses to Design patterns and practices in .NET: the State pattern

  1. Pingback: State Pattern | C#Net

  2. Pingback: Architecture and patterns | Michael's Excerpts

  3. Dobromir says:

    Hello, how good is to have a Class with method that do nothing but throws an exception?

  4. Thierry says:

    Hi Andras, while I understand what you’ve done, I’m not sure on what to do with each newly created class that represent each state? How and when do you call these? What do I compare these classes to? Can you clarify? Thanks

    • Andras Nemes says:

      Hi Thierry, the order will have some kind of state at any time that can be retrieved from a data store. Based on the current state you can build logic for the next state. You can place that logic within the order class or if it’s too complex then in a factory which will return an IOrderState object based on the current state. //Andras

  5. Donald Driver says:

    Why allow clients to call methods that throw exceptions on impossible scenarios? For example, why can a client call an instance of ShippedState.Ship()? It seems that there is some polymorphism for polymorphism’s sake here. Most of the inherited classes would just throw exceptions.

  6. Andriy P says:

    Hi Andras,
    Is it a required condition that IOrderState methods use Order object? like in Cancel(Order order) e t.c. If Order aggregate IOrderState already, wouldn’t we get circular dependency here?
    Thanks!

  7. Pingback: State Design Pattern |

  8. Jason Price says:

    Thanks for the article. There is a comment that says //actual shipping logic ignored, only changing the status. In a real application, would the state pattern do more than just change the status? Would it implement shipping logic, for instance?

  9. Bala says:

    How do you go about handling business logic inside the state transitions. For example in order to move from stateA to stateB we need to make some repository calls, API calls and then based on the results we make transition decisions. Currently in the example above, you have an Order object and it is the one which decides on state transition and image you have a very complicated business logic where you have so many dependencies to make decisions on state transition, currently everything will end up in the order class and makes t one massive monolith class. Is there any better way to do this

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.