DEV Community

mohamed Tayel
mohamed Tayel

Posted on

Understanding the State Design Pattern P3

Meta Description: Learn how to implement a simple state machine in C# using a switch statement without relying on external libraries. This article walks through a combination lock example, demonstrating how to manage states and transitions in a readable and efficient way, ideal for small applications.

Introduction

State machines can often be overly complex, especially when using centralized structures like dictionaries to manage transitions. In this article, we’ll take a simpler approach to state machines using a switch statement to handle states and transitions. We’ll demonstrate this approach by modeling a combination lock with three states: Locked, Failed, and Unlocked.

Why Simplify the State Machine?

In many cases, defining a state machine with a dictionary of transitions adds unnecessary complexity. Instead, using a switch statement allows us to manage state transitions inline, making the code more readable and straightforward. With this approach, we avoid the need for a centralized data structure to manage states, making the logic easier to follow, especially for simple state machines.

Example Scenario: Combination Lock

In this example, a combination lock will have the following states and behaviors:

  • Locked: The initial state where the lock is locked.
  • Failed: The state when an incorrect code is entered.
  • Unlocked: The state when the correct code is entered, unlocking the lock.

Implementation Steps

Let’s walk through the steps to create this combination lock using a switch statement in C#.


Step 1: Define States and Variables

We’ll start by defining the states using an enum. Then, we’ll set up a few initial variables:

  • Correct Code: The combination that unlocks the lock.
  • Initial State: Set to Locked.
  • Entry Builder: Stores the user’s input as they attempt to unlock the combination.
using System;
using System.Text;

namespace SimpleStateMachineExample
{
    // Define the states of the lock
    public enum State
    {
        Locked,
        Failed,
        Unlocked
    }

    class CombinationLock
    {
        private readonly string _correctCode = "1234";
        private State _currentState = State.Locked;
        private StringBuilder _entry = new StringBuilder();

        public void Run()
        {
            while (true)
            {
                switch (_currentState)
                {
                    case State.Locked:
                        HandleLockedState();
                        break;
                    case State.Failed:
                        HandleFailedState();
                        break;
                    case State.Unlocked:
                        HandleUnlockedState();
                        return; // End program after unlocking
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Implement Each State’s Logic

Each state requires different logic. We’ll implement separate methods to handle the behavior of each state: Locked, Failed, and Unlocked.

  1. Locked State: In this state, we wait for the user to input digits for the combination. If the entry matches the correct code, we transition to the Unlocked state. If a digit is incorrect, we switch to the Failed state.

  2. Failed State: If the user enters the wrong code, this state resets the entry and allows the user to try again by returning to the Locked state.

  3. Unlocked State: Displays an "Unlocked" message and exits the program, as the lock is now open.

class CombinationLock
{
    // Handles the Locked state behavior
    private void HandleLockedState()
    {
        Console.Write("Enter a digit: ");
        char input = Console.ReadKey().KeyChar;
        _entry.Append(input);

        // Check if entry matches the correct code so far
        if (_correctCode.StartsWith(_entry.ToString()))
        {
            if (_entry.ToString() == _correctCode)
            {
                _currentState = State.Unlocked;
            }
        }
        else
        {
            _currentState = State.Failed;
        }
        Console.WriteLine();
    }

    // Handles the Failed state behavior
    private void HandleFailedState()
    {
        Console.CursorLeft = 0;
        Console.WriteLine("Failed");
        _entry.Clear();
        _currentState = State.Locked;
    }

    // Handles the Unlocked state behavior
    private void HandleUnlockedState()
    {
        Console.CursorLeft = 0;
        Console.WriteLine("Unlocked");
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Run the State Machine

In the Main method, we initialize the CombinationLock class and start the state machine by calling the Run method.

class Program
{
    static void Main(string[] args)
    {
        var combinationLock = new CombinationLock();
        combinationLock.Run();
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation of the Code

  1. Switch Statement: The Run method uses a switch statement to manage transitions between states, calling the appropriate method based on the current state.

  2. HandleLockedState: This method reads each digit as the user enters it, adding it to the entry. If the input matches the correct code, it transitions to the Unlocked state; otherwise, it goes to the Failed state.

  3. HandleFailedState: Clears the entry, allowing the user to start over from the Locked state.

  4. HandleUnlockedState: Prints "Unlocked" to indicate success and exits the program.

Example Walkthrough

When you run the code, you can try different inputs to see how the lock behaves:

  • Correct Code: Enter "1234" in sequence, and the lock displays "Unlocked."
  • Incorrect Code: Enter any wrong digit, and the lock will display "Failed" and reset, allowing you to try again.

Benefits of Using a Switch-based State Machine

This approach has several advantages:

  1. Simplicity: The logic is straightforward and easy to follow, as each state’s behavior is managed within a single method.
  2. Readability: The switch statement makes it clear what actions are available in each state.
  3. Flexibility: Small state machines like this don’t require complex libraries, making them easier to modify or extend.

Conclusion

Using a switch statement for simple state machines can be a great alternative to centralized dictionaries or libraries. This approach keeps the code easy to read and maintain while offering the flexibility to handle small, finite state systems efficiently. For complex applications, libraries may offer additional features, but this approach is ideal for smaller state machines.

This example shows how you can model state transitions naturally without needing a formalized structure, providing a lightweight solution for simple state-based logic in C#.

Top comments (0)