State pattern in C#

 Hello, C# lovers! 😍

Today I want to share with you one of the most powerful design patterns: the State pattern. 🙌

The State pattern is a behavioral pattern that lets an object change its behavior dynamically based on its internal state. 🤔

This is very handy when you have an object that can be in different states, and each state requires different actions or responses. For example, imagine a gambling terminal that can be in one of these states: idle, waiting for coins, ready to play, playing, paying out, or out of order. 💰

Each state has its own logic and rules, and the terminal should behave accordingly. For example, when the terminal is idle, it should display a welcome message and wait for coins. When it is waiting for coins, it should accept more coins or let the user select a game. When it is ready to play, it should allow the user to start playing. When it is playing, it should process the game logic and end the game. When it is paying out, it should dispense the winnings and return to idle. When it is out of order, it should display an error message and reject any coins. 🎰

How can we implement this with the State pattern? Well, the idea is to create an abstract class or interface that represents the common behavior of all states. Then, we create concrete subclasses or implementations for each specific state. Finally, we have a context class that holds a reference to the current state object and delegates all requests to it. 🧐

Here is a simple example in C#:


// The abstract state class
public abstract class State
{
    // The context class
    protected GamblingTerminal terminal;

    // A constructor that takes the context as a parameter
    public State(GamblingTerminal terminal)
    {
        this.terminal = terminal;
    }

    // Some abstract methods that define the common behavior of all states
    public abstract void InsertCoin();
    public abstract void SelectGame();
    public abstract void StartGame();
    public abstract void EndGame();
    public abstract void PayOut();
}

// A concrete state class for the idle state
public class IdleState : State
{
    // Calling the base constructor
    public IdleState(GamblingTerminal terminal) : base(terminal) {}

    // Implementing the abstract methods
    public override void InsertCoin()
    {
        // If a coin is inserted, change the state to waiting for coins
        terminal.SetState(new WaitingForCoinsState(terminal));
        Console.WriteLine("Coin inserted. Please insert more coins or select a game.");
    }

    public override void SelectGame()
    {
        // If a game is selected, do nothing (the terminal is not ready to play)
        Console.WriteLine("Please insert coins first.");
    }

    public override void StartGame()
    {
        // If the game is started, do nothing (the terminal is not ready to play)
        Console.WriteLine("Please insert coins and select a game first.");
    }

    public override void EndGame()
    {
        // If the game is ended, do nothing (the terminal is not playing)
        Console.WriteLine("No game in progress.");
    }

    public override void PayOut()
    {
        // If the payout is requested, do nothing (the terminal has no winnings)
        Console.WriteLine("No winnings to pay out.");
    }
}

// A concrete state class for the waiting for coins state
public class WaitingForCoinsState : State
{
    // Calling the base constructor
    public WaitingForCoinsState(GamblingTerminal terminal) : base(terminal) {}

    // Implementing the abstract methods
    public override void InsertCoin()
    {
        // If a coin is inserted, add it to the balance and display it
        terminal.Balance += 1;
        Console.WriteLine($"Coin inserted. Current balance: {terminal.Balance}.");
    }

    public override void SelectGame()
    {
        // If a game is selected, check if there is enough balance to play
        if (terminal.Balance >= 2)
        {
            // If there is enough balance, change the state to ready to play and display the game name
            terminal.SetState(new ReadyToPlayState(terminal));
            Console.WriteLine($"Game selected: {terminal.GameName}. Press start to play.");
        }
        else
        {
            // If there is not enough balance, display a message and ask for more coins
            Console.WriteLine("Not enough balance to play. Please insert more coins.");
        }
        
    }

    public override void StartGame()
    {
        // If the game is started, do nothing (the terminal is not ready to play)
        Console.WriteLine("Please select a game first.");
    }

    public override void EndGame()
    {
        // If the game is ended, do nothing (the terminal is not playing)
        Console.WriteLine("No game in progress.");
    }

    public override void PayOut()
    {
        // If the payout is requested, do nothing (the terminal has no winnings)
        Console.WriteLine("No winnings to pay out.");
    }
}

// Other concrete state classes omitted for brevity
// You can find them in this awesome article: https://refactoring.guru/design-patterns/state/csharp/example

// The context class
public class GamblingTerminal
{
    // A private field that holds the current state object
    private State state;

    // Some public properties that store the terminal data
    public int Balance { get; set; }
    public string GameName { get; set; }
    public int Winnings { get; set; }

    // A constructor that sets the initial state to idle
    public GamblingTerminal()
    {
        state = new IdleState(this);
        Console.WriteLine("Welcome to the gambling terminal!");
    }

    // A method that allows changing the state object
    public void SetState(State state)
    {
        this.state = state;
    }

    // Some methods that delegate the requests to the current state object
    public void InsertCoin()
    {
        state.InsertCoin();
    }

    public void SelectGame()
    {
        state.SelectGame();
    }

    public void StartGame()
    {
        state.StartGame();
    }

    public void EndGame()
    {
        state.EndGame();
    }

    public void PayOut()
    {
        state.PayOut();
    }
}


As you can see, the State pattern allows us to encapsulate the logic of each state in a separate class, and avoid using complex conditional statements or switch cases in the context class. This makes our code more modular, flexible, and easy to maintain. 😎

The State pattern is also very useful when we need to add new states or modify existing ones. We just need to create or modify the corresponding state class, without affecting the rest of the code. 🙌

The State pattern is a standard C# implementation, and you can find more information and examples in the official documentation: https://docs.microsoft.com/en-us/dotnet/standard/modern-web-apps-azure-architecture/implementing-the-state-pattern

If you want to learn more about the State pattern and other design patterns, I highly recommend these resources:



I hope you enjoyed this article and learned something new. Please leave your comments and questions below, and don’t forget to subscribe to my blog for more awesome C# content. 😊


You can find the code examples here: source code


Happy coding! 🚀

Comments

Popular posts from this blog

Which GOF patterns are good for C#?

Angular on a regular SharePoint page (part 2)

Chain of Responsibility pattern in C#