DEV Community

Cover image for Understanding the State Pattern
Spyros Ponaris
Spyros Ponaris

Posted on

Understanding the State Pattern

Understanding the State Pattern with Calculator

🛠 Introduction
The State Pattern allows an object to change its behavior dynamically based on its internal state. It’s a powerful design pattern for handling complex, state-dependent behavior in an organized and maintainable way.

In this article, we’ll explore the State Pattern by implementing a simple Calculator with multiple states.

📚 Calculator State Example
1️⃣ State Interface
The State interface defines the contract that all concrete state classes must implement. It contains methods that represent the state-specific behavior.

public interface ICalculatorState {
    void EnterNumber(CalculatorContext context, int number);
    void PerformOperation(CalculatorContext context, string operation);
}
Enter fullscreen mode Exit fullscreen mode

2️⃣ Concrete States
Concrete states implement the State interface. Each state encapsulates the behavior for a specific condition of the Context.

NumberState: Handles behavior when the calculator expects a number.

public class NumberState : ICalculatorState {
    public void EnterNumber(CalculatorContext context, int number) {
        context.CurrentValue = number;
        Console.WriteLine($"Number entered: {number}");
    }

    public void PerformOperation(CalculatorContext context, string operation) {
        Console.WriteLine("Operation not allowed in Number State.");
    }
}

Enter fullscreen mode Exit fullscreen mode

OperationState: Handles behavior when the calculator performs an operation.

public class OperationState : ICalculatorState {
    public void EnterNumber(CalculatorContext context, int number) {
        context.SecondValue = number;
        Console.WriteLine($"Second number entered: {number}");
    }

public void PerformOperation(CalculatorContext context, string operation) {
        switch (operation) {
            case "+":
                context.Result = context.CurrentValue + context.SecondValue;
                break;
            case "-":
                context.Result = context.CurrentValue - context.SecondValue;
                break;
            default:
                Console.WriteLine("Unsupported operation.");
                return;
        }
        Console.WriteLine($"Operation performed: {context.Result}");
        context.SetState(new NumberState()); // Transition back to NumberState
    }
}

Enter fullscreen mode Exit fullscreen mode

3️⃣ Context
The Context is the core class that ties everything together. It keeps track of the current state and delegates behavior to the current state object.

public class CalculatorContext {
    private ICalculatorState _state;
    public int CurrentValue { get; set; }
    public int SecondValue { get; set; }
    public int Result { get; set; }

    public CalculatorContext() {
        _state = new NumberState(); // Start in the NumberState
    }

    public void SetState(ICalculatorState state) {
        _state = state;
    }

    public void EnterNumber(int number) {
        _state.EnterNumber(this, number);
    }

    public void PerformOperation(string operation) {
        _state.PerformOperation(this, operation);
    }
}
Enter fullscreen mode Exit fullscreen mode

4️⃣ Main Program
Here’s how it all comes together in the main program:

class Program {
    static void Main(string[] args) {
        CalculatorContext calculator = new CalculatorContext();

        calculator.EnterNumber(10);
        calculator.SetState(new OperationState());
        calculator.EnterNumber(20);
        calculator.PerformOperation("+");
    }
}
Enter fullscreen mode Exit fullscreen mode

Output :

Number entered: 10
Second number entered: 20
Operation performed: 30
Enter fullscreen mode Exit fullscreen mode

✅ Conclusion
The State Pattern is an effective solution for managing state-dependent behavior in software applications. By encapsulating state-specific logic within individual classes, it promotes cleaner, modular, and maintainable code.

The Calculator example demonstrates how this pattern allows dynamic state transitions, ensuring the application behaves correctly as its state evolves. This approach is especially useful in scenarios requiring flexibility and extensibility, such as games, workflows, or applications with complex user interactions.

References

Top comments (0)