State patter or state design pattern is one of the design patterns introduced by the Gang of four teams. This pattern falls under the category of behavioral design pattern.

In many cases, you might need to design the state machine in your application. A state machine is a very known concept in computer engineering. The state pattern is a way of implementing the state machine using an object-oriented fashion.

Hi folks, In this article, we will try to understand the state pattern, what, why, and when to use this pattern. Also will implement one of the examples using c#.

What is the state design pattern?

The state design pattern is a scheme used to model changes in the state (or states) of an object. It delegates rules for such changes to individual objects representing each possible state.

When should we Use the State Model?

An object with a relatively complex set of possible states applies the state model. Especially applied for an object with multiple business rules on how state transitions occur and what happens next.

In addition, you can update any state at any time with a special logic if the object is merely a state-owned property. This increases a needless complication in the State Schema. However, for the objects representing real-world concepts, with a compound workflow, state structure can be a decent option.

What are the Advantages of Using a State Design Pattern Model?

To begin with, a major advantage of the state model is its capability to minimize conditional complexity. It eliminates the need for it and switches statements on objects with different behavioral requirements, unique to different state transitions. So, representing an object’s state using a finite state machine diagram simplifies the conversion of the diagram into a state design model’s types & methods.

What are the Disadvantages of Using this Model?

A developer needs to write a large amount of code for the state schema. Depending on the number of different defined state transition methods and possible object states, you can write numerous methods. Thus, for N states with M transition methods, the total number of methods required will be (N+1)*M.

An insurance policy with 5 different states and 5 methods for each (ignoring the ListValidOperations method) requires 25 methods in total. The policy context type must also define the 5 state transition methods, increasing the total methods required to 30.

State Design Pattern , Real Time Example

  • Coin or card bases check-in gate. This machine can be found in stations, offices, airports, parking areas, etc. This machine is the best example of utilizing the state design pattern.
  • ATM machine, you can not perform any transaction until you swipe the card, and change the machine state to card swiped.
  • TV remote, by using the remote you can change the channel, increase volume, and put on mute. But to do all these operations the state of TV should be switched on.

UML diagram of State design pattern

State Design Pattern

Let’s understand the components of the state design patterns using the UML diagram.

Participants

    The classes and objects participating in this pattern are:

  • Context
    • defines the interface of interest to clients
    • maintains an instance of a ConcreteState subclass that defines the current state.
  • State  
    • defines an interface for encapsulating the behavior associated with a particular state of the Context.
  • Concrete State  
    • each subclass implements a behavior associated with a state of Context

Hands-on With Bank Account Problem Statement

We are going to implement the state design pattern for one of our banking clients. Where bank accounts will have different-2 states based on the available balance in the account.

Bank Account Problem Statement using State Design Pattern

A client has come up with these requirements.

  1. A new account will have a status called Silver account.
  2. An account with more than 10000 Rs will have a status called Gold Account.
  3. An account that has less than 1000 Rs will have a status called Red Account.

Let’s start building the interfaces and abstract classes for our application. As we can see in the above UML diagram.

State interface with Request handling methods and current state information.

using StateDesignPatternDemo.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace StateDesignPatternDemo.States
{
    public interface IState
    {
        IAccountContext AccountContext { get; set; }
        double Balance { get; set; }
        void Deposit(double amount);
        void Withdraw(double amount);
        string StateInfo { get; }
    }
}

IContext interface for maintaining the current state information and other details related to account

using StateDesignPatternDemo.States;
using System;
using System.Collections.Generic;
using System.Text;
namespace StateDesignPatternDemo.Interfaces
{
    public interface IAccountContext
    {
        Guid AccountId { get; set; }
        double AccountBalance { get; }
        IState State { get; set; }
        void Deposit(double ammount);
        void Withdraw(double ammount);
    }
}

We can implement common methods like Deposite and WithDraw using an abstract class.

using System;
using System.Collections.Generic;
using System.Text;
using StateDesignPatternDemo.Interfaces;
namespace StateDesignPatternDemo.States
{
    public abstract class StateBase : IState
    {
        protected StateBase(double balance)
        {
            Balance = balance;
        }
        public double Balance { get; set; }
        public IAccountContext AccountContext { get; set; }
        public virtual string StateInfo
        {
            get
            {
                return this.GetType().Name;
            }
        }
        protected abstract void Handle(IAccountContext accountContext);
        public void Deposit(double amount)
        {
            Balance += amount;
            Handle(this.AccountContext);
        }
        public void Withdraw(double amount)
        {
            Balance -= amount;
            Handle(this.AccountContext);
        }
    }
}

As you can see in the above class Statebase, we are implementing the deposit and withdraw methods. These methods will call the abstract method Handle, this method will be implemented by the concrete implementation of state classes.

Implement the IAccountContext interface as well. This will be the abstraction layer for the user. Users can perform only two operations Deposit and Withdraw the amount.

using StateDesignPatternDemo.Interfaces;
using StateDesignPatternDemo.States;
using System;
using System.Collections.Generic;
using System.Text;
namespace StateDesignPatternDemo.Impl
{
    public class AccountContext : IAccountContext
    {
        public AccountContext(Guid accountId, IState state)
        {
            this.AccountId = accountId;
            this.State = state;
            this.State.AccountContext = this;
        }
        public IState State { get; set; }
        public Guid AccountId { get; set; }
        public double AccountBalance => State.Balance;
        /// <summary>
        ///
        /// </summary>
        /// <param name="ammount"></param>
        public void Deposit(double ammount)
        {
            State.Deposit(ammount);
            Console.WriteLine("Account Current Balance :" + AccountBalance);
            Console.WriteLine("Account Current State   :" + State.StateInfo);
            Console.WriteLine();
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="ammount"></param>
        public void Withdraw(double ammount)
        {
            State.Withdraw(ammount);
            Console.WriteLine("Account Current Balance :" + AccountBalance);
            Console.WriteLine("Account Current State   :" + State.StateInfo);
            Console.WriteLine();
        }
    }
}

Understanding the AccountContext class

  • AccountContext class has one constructor which takes two parameters AccountId and IState object.
    • AccountId is the one that will be provided at the time of account creation.
    • IState is the initial state of an account.
    • AccountBalance property will have the details about the current balance in the account.
    • Deposit and Withdraw methods will call the methods implemented by the state object.

As per the current client requirement, We have three states of the object. Red, Silver, and Gold.

Implement the SilverState of the account.

using System;
using System.Collections.Generic;
using System.Text;
using StateDesignPatternDemo.Interfaces;
namespace StateDesignPatternDemo.States
{
    /// <summary>
    ///
    /// </summary>
    public class SilverState : StateBase
    {
        public SilverState(IState state) : this(state.Balance)
        {
            base.AccountContext = state.AccountContext;
        }
        public SilverState(double balance) : base(balance)
        {
        }
        protected override void Handle(IAccountContext accountContext)
        {
            if (accountContext.AccountBalance < 0)
            {
                accountContext.State = new RedState(this);
            }
            else if (accountContext.AccountBalance > 100000)
            {
                accountContext.State = new GoldState(this);
            }
        }
    }
}

Every concrete state has its own logic and based on that it will assign the next state the context.

Silver State has two conditions

  1. If the balance is less than zero, it will change the account state to Redstate.
  2. If the balance is greater than 100000, it will change the account state to GoldState.

Implement the RedState class

using System;
using System.Collections.Generic;
using System.Text;
using StateDesignPatternDemo.Interfaces;
namespace StateDesignPatternDemo.States
{
    public class RedState : StateBase
    {
        public RedState(IState state) : this(state.Balance)
        {
            base.AccountContext = state.AccountContext;
        }
        public RedState(double balance):base(balance)
        {
        }
        protected override void Handle(IAccountContext accountContext)
        {
            if (accountContext.AccountBalance > 0)
            {
                accountContext.State = new SilverState(this);
            }
        }
    }
}

RedState has only one condition

  1. If the balance is greater than zero, assign a silver state to the account.

Implement the GoldState class

using System;
using System.Collections.Generic;
using System.Text;
using StateDesignPatternDemo.Interfaces;
namespace StateDesignPatternDemo.States
{
    public class GoldState : StateBase
    {
        public GoldState(IState state) : this(state.Balance)
        {
            base.AccountContext = state.AccountContext;
        }
        public GoldState(double balance):base(balance)
        {
        }
        protected override void Handle(IAccountContext accountContext)
        {
            if (accountContext.AccountBalance < 100000&& accountContext.AccountBalance>0)
            {
                accountContext.State = new SilverState(this);
            }
            else if (accountContext.AccountBalance <0)
            {
                accountContext.State = new RedState(this);
            }
        }
    }
}

The GoldState also has two conditions

  1. If the account balance is less than100000 and greater than zero, Assign Silverstate as the current state o the account.
  2. If the account balance is zero, Assign Redstate to the account.

We are done with implementing all the concrete implementation of state classes. Now we are ready to invoke the create the account and do some transactions on that account.

You can create a new instance of AccountContext class, and start calling the deposit and withdraw.

using StateDesignPatternDemo.Impl;
using StateDesignPatternDemo.Interfaces;
using StateDesignPatternDemo.States;
using System;
namespace StateDesignPatternDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var accountContext = new AccountContext(Guid.NewGuid(), new SilverState(0.0));
            accountContext.Deposit(1000);
            accountContext.Deposit(10000);
            accountContext.Deposit(90000);
            accountContext.Withdraw(90000);
            accountContext.Withdraw(90000);
            accountContext.Deposit(90000);
            accountContext.Deposit(90000);
        }
    }
}

As you can see, we are just calling the Deposit and amount and withdrawing the amount from the account. I don’t have to worry about validating the current state and reassigning it each time we perform some transactions.

Enhancements in the given solution

  • We can utilize the DI container to make this application loosely coupled.
  • In some places, I have used hardcoded values like 10000 and 0 that can be removed and should have been taken from some data source.
  • StateInfo property can be assigned using a constructor or setter property.

you are most welcome to make changes to the solution and contribute to the project for learning purposes.

Conclusion

Advantage of using a state design pattern, In the future, if the client requests to add one more state to the account. We don’t have to change the existing code, however, we can always implement the new state, which will be assigned automatically to the accounts.

81 / 100