Sitemap

C# Design Patterns: State

5 min readJul 12, 2024

--

Press enter or click to view image in full size
State C# Design Pattern

Thanks for visiting this article. Please scroll down for the bite-sized explanation of this pattern.

Definition

The State design pattern is a behavioural design pattern that encourages encapsulating state-specific behaviour into separate classes instead of defining states in a monolithic class with conditionals.

This approach makes the code more maintainable and scalable, especially when adding, modifying, or removing states.

When to use?

This pattern is suitable for the following cases:

✅ When an object has different behaviour depending on its state,

✅ When the number of states may frequently change and can extend over time.

When not to use?

❌ When the states of an object are fixed and do not need to change or rarely change over time.

❌ When the number of states is manageable and does not need the behaviours, encapsulated in multiple classes.

Example

Since this pattern is suitable for objects with state-specific behaviours, let’s try to understand this by a simple e-commerce “Order”.

An Order can be created, processed, shipped, delivered and cancelled (only when the order is still under processing).

Let’s create an Order class having an enum called OrderState and a property State to keep track of the current state.

For the sake of less code, we will consider a single order instance to deal with…you are welcome to introduce an IDfor extending the behaviours based on multiple orders IDsfor each state

Note: The code examples below are written as part of this blog and could contain syntax errors. The main goal of these examples is to deliver an understandable concept.

Press enter or click to view image in full size
Order class

We will call the required behaviours by the following method names:

  • NewOrder (can be replaced by the constructor),
  • ProcessOrder,
  • ShipOrder,
  • DeliverOrder,
  • and CancelOrder.

These methods will define the behaviours based on the current order state and will be responsible for updating the state when required.

Press enter or click to view image in full size
Order class with all of the possible state-specific behaviours

Finally, let’s call these methods from our client Shop class, to have an order created and delivered 😏

Press enter or click to view image in full size
Shop client class

This implementation covers the common order behaviours, any e-commerce platform nowadays has to provide. 💁

The problem with the above approach

❌ Tight Coupling: All state-related behaviour is in one class.

❌ Complexity: Adding new states or changing state behaviour requires modifying the existing class.

❌ Maintenance: Difficult to maintain as the number of states and transitions grows.

Get Biswajit Sahoo’s stories in your inbox

Join Medium for free to get updates from this writer.

But as the e-commerce market is ever-growing, we can imagine customers wanting more-n-more flexibility with their orders, for example:

  • allowing them to return an ordered product,
  • or even modify an ordered product and so on…

As any senior developer, now you need to modify your app’s functionality to accommodate the new behaviours and states for an Order 😤 keeping in mind the code quality and ability to scale it afterwards. ⛳️

And that clearly cannot be possible by jamming all the possible states in one single-order class (you know where this is going 😙).

Hence comes the State design pattern to rescue.

Implementing the State Pattern

The State design pattern states that if you have many state-dependent behaviours to manage for an object, then shift them to their respective classes and have them automatically handle their behaviours.

Before implementing the state pattern, let’s understand the components required:

  • State Abstraction / Interface — typically declares methods representing the state-specific behaviour of the object.
  • Concrete classes — These refer to the individual classes we are going to create for each of the possible states and implement the State abstraction/interface.
  • Context class — This is the object class itself which maintains a reference to its current state. It will act as an interface for clients to interact with and delegate state-specific behaviour to the current state object. (will make more sense as you read along 😃 )

We will call the interface, IOrderState.

Press enter or click to view image in full size
IOrderState interface

With the interface defined, let’s create the individual states (Concrete classes) and move the state-specific behaviours within the respective classes,

Press enter or click to view image in full size
NewOrderState concrete state class
Press enter or click to view image in full size
ProcessingOrderState concrete state class
Press enter or click to view image in full size
ShippedOrderState concrete state class
Press enter or click to view image in full size
DeliveredOrderState concrete state class
Press enter or click to view image in full size
CancelledOrderState concrete state class

With the concrete states created, you may be wondering what is this order and how we set the state on it using order.SetState(IOrderState) ?

Well, that’s the final context class we are going to create, called Order.

Press enter or click to view image in full size
Order context class

And all, we have to do now…is to use the same Shop class (client) we had created earlier,

Press enter or click to view image in full size
Shop client class

With the implementation complete, you can see the clear advantage of this design pattern over the traditional approach:

  1. ✅ Encapsulation: State-specific behaviour is encapsulated in individual state classes.
  2. ✅ Ease of Maintenance: Adding or modifying states does not affect the Order class.
  3. ✅ Flexibility: Easily add new states or change the behaviour of existing states.

But this design pattern is not all shiny 🌟 and contains some obvious disadvantages ⛅️, like:

  • ⚠️ Increased Number of Classes: More classes are created for each state.
  • ⚠️ ️Initial Complexity: Setting up the pattern requires more initial effort.
  • ⚠️ State awareness: Concrete state classes need to be aware of the other state types, for them to set the correct state when needed.

something to be kept in mind when choosing this pattern to implement.

The above disadvantages can be mitigated by doing some additional work.

And that completes this example of explaining how and when to use the State design pattern in your project. 😄

Hope this helped with giving you some clarity on how this pattern can be implemented and used in a real-world scenario.

On the last note. If you like the article please do like and share, and also feel free to provide your valuable comments and feedback.

--

--

Biswajit Sahoo
Biswajit Sahoo

Written by Biswajit Sahoo

Software dev who writes about C#, AI, Unity, and XR or anything trending in simpler words.