Mastering State Design Pattern in C# for Beginners

Özkan ARDİL
10 min readMay 14, 2024

--

State design pattern in c#

Using the State Design Pattern in C#, you can allow an object to alter its behavior when its internal state changes.

In this article, we’ll;

  • Explore State Design Pattern from class diagrams to real-world code examples,
  • Unveil its advantages,
  • Dissect the disadvantages,
  • Ensure it aligns with the SOLID principles.

By the end, you’ll be the master State Design Pattern, enhancing your software with finesse.

The State Design Pattern holds significant importance in software development, particularly in enhancing the flexibility and maintainability of code. This design pattern allows an object to alter its behavior when its internal state changes, treating each state as a separate class. By encapsulating each state in a distinct class, the object can delegate state-specific functionalities, making it easier to add new states or modify existing ones without altering the object’s structure.

Moreover, the State pattern promotes cleaner and more modular code by centralizing the logic related to each state. This not only simplifies the understanding of the codebase but also facilitates the addition of new states in the future. Additionally, the State pattern supports the Open/Closed Principle, enabling developers to extend the system’s behavior without modifying existing code.

Transitioning to concrete examples, consider the application of the State pattern in the context of a satellite connection, such as a satellite internet link. Just as the pattern manages the various states of a network connection, it also facilitates the seamless management of a satellite system’s diverse states, improving the overall design and maintenance of the software.

Similarly, envision the application of the State pattern in everyday devices like a thermostat or a smartphone. By adopting this design approach, these devices can efficiently manage different states, responding to user inputs appropriately. In the case of a thermostat, for instance, the State pattern can be employed to handle various temperature settings, allowing for a more dynamic and adaptable system.

In essence, the State Design Pattern provides a structured and extensible way to manage the behavior of objects based on their internal states, contributing to the overall robustness and scalability of software systems.

Class Diagram of State Design Pattern

You will find the class diagram below.

Class diagram of State Design Pattern

Elements

  • Tv class: The Tv class serves as the context or container for the various states of the television. It maintains a reference to the current state object and delegates state-specific behavior to that object. This class encapsulates the functionality related to the television's different states, allowing for a more modular and maintainable code structure.
  • IPossibleStates interface: The IPossibleStates interface defines a set of methods that represent the possible behaviors or actions associated with different states of the television. Each concrete state class implements this interface, ensuring a consistent set of methods across all state classes. This interface acts as a contract that enforces the implementation of state-specific behavior in each concrete state class.
  • Concrete classes (Mute, On, and Off): The concrete classes, such as Mute, On, and Off, implement the IPossibleStates interface and represent specific states of the television. Each class encapsulates the behavior associated with its respective state. For example, the Mute class defines how the television behaves when it is in a muted state, while the On and Off classes handle the behaviors when the television is switched on or off, respectively. These classes facilitate a clean separation of concerns, making it easy to add or modify states without affecting the overall structure of the Tv class.

Solution Explorer

You will find the high-level structure of the parts of the program.

Solution explorer of the State Design Pattern sample project

Implementation

In the context of managing the states of a television, the implementation involves the entities above.

So, let’s create the code for the elements of the sample project.

Tv class.

  public class Tv
{
private IPossibleStates _currentState;
public IPossibleStates CurrentState
{

get
{
return _currentState;
}

set
{
_currentState = value;
}
}
public Tv()
{
_currentState = new Off(this);
}
public void PressOffButton()
{
_currentState.PressOffButton(this);//Delegating the state
}
public void PressOnButton()
{
_currentState.PressOnButton(this);//Delegating the state
}
public void PressMuteButton()
{
_currentState.PressMuteButton(this);//Delegating the state
}
}

IPossibleStates interface.

 public interface IPossibleStates
{
void PressOnButton(Tv context);
void PressOffButton(Tv context);
void PressMuteButton(Tv context);
}

On.cs class implements the IPossibleStates interface.

   public class On: IPossibleStates
{
Tv _tvContext;
public On(Tv context)
{
Console.WriteLine("Tv is On now.");
_tvContext = context;
}

public void PressOnButton(Tv context)
{
Console.WriteLine("On button is pressed. Tv is already in On state.");
}

public void PressOffButton(Tv context)
{
Console.WriteLine("Off button is pressed. Going from On to Off state.");

_tvContext.CurrentState = new Off(context);
}

public void PressMuteButton(Tv context)
{
Console.WriteLine("Mute button is pressed. Going from On to Mute mode.");
_tvContext.CurrentState = new Mute(context);
}
}

Off.cs class implements the IPossibleStates interface.

   public class Off: IPossibleStates
{
Tv _tvContext;
//Initially we'll start from Off state
public Off(Tv context)
{
Console.WriteLine("Tv is Off now.");
_tvContext = context;
}
//Users can press any of these buttons at this state-On, Off or Mute
//Tv is Off now, user is pressing On button
public void PressOnButton(Tv context)
{
Console.WriteLine("On button is pressed. Going from Off to On state");

_tvContext.CurrentState = new On(context);
}
//Tv is Off already, user is pressing Off button again
public void PressOffButton(Tv context)
{
Console.WriteLine("Off button is pressed. Tv is already in Off state");
}

public void PressMuteButton(Tv context)
{
Console.WriteLine("Mute button is pressed. Tv is already in Off state, so Mute operation will not work.");
}
}

Mute.cs class implements the IPossibleStates interface.

   internal class Mute: IPossibleStates
{
Tv _tvContext;
public Mute(Tv context)
{
Console.WriteLine("Tv is in Mute mode now.");
_tvContext = context;
}
//Users can press any of these buttons at this state-On, Off or Mute
//Tv is in mute, user is pressing On button
public void PressOnButton(Tv context)
{
Console.WriteLine("On button is pressed. Going from Mute mode to On state.");

_tvContext.CurrentState = new On(context);
}
//Tv is in mute, user is pressing Off button
public void PressOffButton(Tv context)
{
Console.WriteLine("Off button is pressed. Going to Mute mode to Off state.");

_tvContext.CurrentState = new Off(context);
}
//Tv is in mute already, user is pressing mute button again
public void PressMuteButton(Tv context)
{
Console.WriteLine(" Mute button is pressed. Tv is already in Mute mode.");
}
}

So, we put them together within the program.cs class.

Console.WriteLine("***State Pattern Demo***\n");
//Initially Tv is Off
Tv Tv = new Tv();
Console.WriteLine("User is pressing buttons in the following sequence: ");

Console.WriteLine("Off->Mute->On->On->Mute->Mute->Off\n");
//Tv is already in Off state
Tv.PressOffButton();
//Tv is already in Off state, still pressing the Mute button
Tv.PressMuteButton();
//Making the Tv on
Tv.PressOnButton();
//Tv is already in On state, pressing On button again
Tv.PressOnButton();
//Putting the Tv in Mute mode
Tv.PressMuteButton();
//Tv is already in Mute, pressing Mute button again
Tv.PressMuteButton();
//Making the Tv off
Tv.PressOffButton();
// Wait for user
Console.Read();

You will find the output below.

The output of the State Design Pattern sample project

In this C# implementation, the IPossibleStates interface defines the common method HandleOperation, which is implemented by Mute.cs, On.cs, and Off.cs classes.

The Tv.cs class manages the current state and delegates operations to the concrete state objects through the IPossibleStates interface.

The client code demonstrates how the state transitions can be dynamically controlled through the Tv.cs class, showcasing the flexibility of the State Design Pattern in the context of managing television states.

Source Code

You can access the source code of the project on my Design Patterns in C# GitHub repository.

If you can give the repository a star and share the article, you will support me in reaching more people.

Advantages of State Design Pattern

Modularity and Clarity: By representing each state as a separate class, the State pattern promotes modularity. Each state class encapsulates its specific behavior, making the code clearer and more maintainable. It is easier to understand and modify the behavior of individual states without affecting the overall system.

Simplified Transition Logic: State transitions are encapsulated within the state classes, simplifying the transition logic. The context delegates state-specific operations to the current state, and when a transition occurs, the context simply switches to a different state object, leading to cleaner and more organized code.

Flexibility and Extensibility: The State pattern provides flexibility by allowing objects to change their behavior dynamically. The ability to switch between different states at runtime makes the system more adaptable to changing requirements. It also facilitates the addition of new states and the modification of existing ones, offering extensibility.

Improved Testing: Testing becomes more straightforward with the State pattern. Each state class can be tested independently, ensuring that the behavior associated with a specific state is correct. This modular approach enhances the reliability of the overall system.

Reduced Conditional Complexity: Without the State pattern, managing the behavior of an object in response to various states often involves complex conditional statements. The State pattern eliminates the need for extensive conditionals, leading to cleaner, more readable code and reducing the likelihood of errors.

Promotion of Separation of Concerns: The pattern promotes the separation of concerns by isolating the behavior of each state in its own class. This separation enhances code organization and facilitates the identification and resolution of issues related to specific states without affecting others.

Disadvantages of the State Design Pattern

Increased Number of Classes: Implementing the State pattern often results in a larger number of classes, especially when there are numerous states and each state requires its own class. This proliferation of classes can make the codebase more complex and harder to manage.

Potential for Excessive Subclasses: If the number of states in an application is extensive, the pattern may lead to a large number of subclasses, each representing a specific state. Managing a large hierarchy of state classes could become unwieldy and impact code readability.

Context-Awareness: The context (the object whose behavior changes with different states) needs to be aware of all possible states. This awareness can make the context class more complex, and developers must ensure that it can handle transitions seamlessly without introducing errors.

Potential for Tight Coupling: In some cases, there might be tight coupling between the context and state classes. Changes in the interface of the state class may require corresponding modifications in the context class, violating the principle of loose coupling.

Complexity in Understanding State Transitions: If state transitions are intricate and involve multiple conditions, the State pattern might not be the most intuitive solution. The complexity of understanding transitions may increase, especially if the transitions are not well-documented.

Overhead of Object Creation: Creating multiple state objects can introduce overhead, particularly if the states are created frequently. This could impact performance in scenarios where object creation needs to be optimized.

State Design Pattern and SOLID Principles in C#

Single Responsibility Principle (SRP): The State Design Pattern presents a structure where each state is represented by a separate class. This ensures that each state has its own responsibility and behavior within the design. For instance, the state of an order such as “Processing”, “Shipped”, “Delivered” is represented as distinct classes, each managing its own functionality. This implies that each state has a single responsibility and changes only affect the relevant state class, thus adhering to SRP.

Open/Closed Principle (OCP): The State Design Pattern facilitates the addition of new states without modifying existing ones. To add a new state, one simply creates a new state class, and no changes are required to existing states or other components. This allows for the extension of the system with new behaviors without altering existing code, conforming to the principles of OCP.

Liskov Substitution Principle (LSP): The State Design Pattern allows each state to be extended from an interface or an abstract class. This means that any state can be used anywhere in the codebase as long as it implements the interface or extends the abstract class. For example, all classes representing any state of an order inherit from the same interface or abstract class. This ensures interchangeability of states and supports LSP.

Interface Segregation Principle (ISP): The State Design Pattern defines small and specific interfaces or abstract classes, each representing only the behavior relevant to a particular state. This enables an object to interact only with the necessary behavior encapsulated within a state class. For instance, to change the state of an order, one interacts only with the relevant state class, which contains the minimal functionality required for that state. This aligns with ISP.

Dependency Inversion Principle (DIP): The State Design Pattern relies on an abstract interface or class to change an object’s state. This directs dependencies towards abstractions, minimizing dependencies on concrete states. For example, the state of an order is managed through a state interface, and concrete state classes implementing this interface are used. This promotes flexibility and easier maintenance of the code, adhering to DIP.

Conclusion

As we conclude our exploration of the State Design Pattern in C#, it’s evident that it is a versatile and valuable tool in your software development arsenal.

With an understanding of class diagrams, code samples, advantages, and considerations, I hope you’ve gained the expertise to employ State Design Pattern effectively.

Keep coding, keep designing, and keep mastering the art of C# development.

👏 If you found the content useful, I would appreciate it if you could support it with applause (you can also contribute with more than one applause by holding down the clap button for a long time).

⬇️ Check out my other articles.

💬 Let me know in the comment section what have I missed? Where I was wrong?

👨‍👦‍👦 You can also share this article with your friend. Argue a little about it. It is known that the truth is born in a dispute.

🙃 Stay determined, stay focused, and keep going.

Thanks for reading…

--

--

Özkan ARDİL

.NET C# JS and Angular dev with 8+ yrs exp, self-taught & passionate web developer. Sharing tips & experiences in C# and web dev.