Introduction
This article describes what is state pattern, when could we find state pattern useful and how to have a rudimentary
implementation of state pattern in C#.
Background
There are times when we find some scenarios in our application when we need to maintain the state of a sub system.
This state needs to be changed based on some conditions and/or user actions. One way to keep track of such states is
by using the conditional logic in code.
Using conditional logic will get us the desired result but that would result in a code that is less understandable
and is harder to maintain. Also, if we need to add more states in the system then that is very difficult and could create
problems in existing system too.
To tackle such problems efficiently, GoF suggest use of State pattern. GoF defines state pattern as
"Allow an object to alter its behavior when its internal state changes. The object will appear to change its class."
It can also be thought of as an object oriented state machine. Let us see the major classes in this pattern.
Context
: This is the actual object who is accessed by the client. The state of this object needs to be tracked.
State
: This is an interface or abstract class that defines the common behavior associated with all the possible
states of the Context.
ConcreteState
: This class represent a state of the Context. Each state will be represented as one concrete class.
Using the code
Let us try to implement a small example to understand the state pattern in detail. Let us try to implement the software
for a dummy ATM machine. The ATM machine is with us and the Bank has decided to have 3 states for this machine.
- No card in the machine
- Card inserted and has been validated
- Cash withdrawn
Now first let us see how we would implement this solution without using the state pattern. We would create
a class for the ATM and then have the state of the ATM maintained locally. Following code snippet shows how we
could implement such ATM class and keep the state of the machine inside the object itself.
class NoStateATM
{
public enum MACHINE_STATE
{
NO_CARD,
CARD_VALIDATED,
CASH_WITHDRAWN,
}
private MACHINE_STATE currentState = MACHINE_STATE.NO_CARD;
private int dummyCashPresent = 1000;
public string GetNextScreen()
{
switch (currentState)
{
case MACHINE_STATE.NO_CARD:
return GetPinValidated();
break;
case MACHINE_STATE.CARD_VALIDATED:
return WithdrawMoney();
break;
case MACHINE_STATE.CASH_WITHDRAWN:
return SayGoodBye();
break;
}
return string.Empty;
}
private string GetPinValidated()
{
Console.WriteLine("Please Enter your Pin");
string userInput = Console.ReadLine();
if (userInput.Trim() == "1234")
{
currentState = MACHINE_STATE.CARD_VALIDATED;
return "Enter the Amount to Withdraw";
}
return "Invalid PIN";
}
private string WithdrawMoney()
{
string userInput = Console.ReadLine();
int requestAmount;
bool result = Int32.TryParse(userInput, out requestAmount);
if (result == true)
{
if (dummyCashPresent < requestAmount)
{
return "Amount not present";
}
dummyCashPresent -= requestAmount;
currentState = MACHINE_STATE.CASH_WITHDRAWN;
return string.Format(@"Amount of {0} has been withdrawn. Press Enter to proceed", requestAmount);
}
return "Invalid Amount";
}
private string SayGoodBye()
{
currentState = MACHINE_STATE.NO_CARD;
return string.Format("Thanks you for using us, Amount left in ATM: {0}", dummyCashPresent.ToString());
}
}
Now when we run the class we can see that this works perfectly from the functionality perspective.
The problem will come when we need to add more states. Whenever we need to add more states to this
ATM machine we need to open up the ATM machine code and then add more logic inside this class itself
which is a clear violoation of Open-Close priciple. Also since we have all that state managed in form of
conditional logic, there are some possibilities that changing code to add new states will break existing
functionality too i.e. this way is more error prone.
So, what would be the better way to do this? The better way to do this is by implementing the same solution
using state pattern. The only thing that we need to do is "encapsulate what varies". So we will go
ahead and pull the state related logic from the ATM class and create seprate classes for each state.
Let us start by modifying the ATM
class so that it will contain no state related information. It will only
keep an handle to the object which keeps track of its state.
public class ATM
{
public ATMState currentState = null;
public ATM()
{
currentState = new NoCardState(1000, this);
}
public void StartTheATM()
{
while (true)
{
Console.WriteLine(currentState.GetNextScreen());
}
}
}
Now the member variable in this class that is being used as the handle to the state is just an
abstract class. This class will point to the actual concrete implementation of state i.e. the real
state of the machine dynamically.
public abstract class ATMState
{
private ATM atm;
public ATM Atm
{
get { return atm; }
set { atm = value; }
}
private int dummyCashPresent = 1000;
public int DummyCashPresent
{
get { return dummyCashPresent; }
set { dummyCashPresent = value; }
}
public abstract string GetNextScreen();
}
And finally we have all the concrete classes, each for one state of the ATM machine. Lets look
at all the state classes now
The class for NO_CARD
state:
class NoCardState : ATMState
{
public NoCardState(ATMState state)
:this(state.DummyCashPresent, state.Atm)
{
}
public NoCardState(int amountRemaining, ATM atmBeingUsed)
{
this.Atm = atmBeingUsed;
this.DummyCashPresent = amountRemaining;
}
public override string GetNextScreen()
{
Console.WriteLine("Please Enter your Pin");
string userInput = Console.ReadLine();
if (userInput.Trim() == "1234")
{
UpdateState();
return "Enter the Amount to Withdraw";
}
return "Invalid PIN";
}
private void UpdateState()
{
Atm.currentState = new CardValidatedState(this);
}
}
The class for CARD_VALIDATED
state:
class CardValidatedState : ATMState
{
public CardValidatedState(ATMState state)
:this(state.DummyCashPresent, state.Atm)
{
}
public CardValidatedState(int amountRemaining, ATM atmBeingUsed)
{
this.Atm = atmBeingUsed;
this.DummyCashPresent = amountRemaining;
}
public override string GetNextScreen()
{
string userInput = Console.ReadLine();
int requestAmount;
bool result = Int32.TryParse(userInput, out requestAmount);
if (result == true)
{
if (this.DummyCashPresent < requestAmount)
{
return "Amount not present";
}
this.DummyCashPresent -= requestAmount;
UpdateState();
return string.Format(@"Amount of {0} has been withdrawn. Press Enter to proceed", requestAmount);
}
return "Invalid Amount";
}
private void UpdateState()
{
Atm.currentState = new NoCashState(this);
}
}
The class for CASH_WITHDRAWN
state:
class CashWithdrawnState : ATMState
{
public CashWithdrawnState(ATMState state)
:this(state.DummyCashPresent, state.Atm)
{
}
public CashWithdrawnState(int amountRemaining, ATM atmBeingUsed)
{
this.Atm = atmBeingUsed;
this.DummyCashPresent = amountRemaining;
}
public override string GetNextScreen()
{
UpdateState();
return string.Format("Thanks you for using us, Amount left in ATM: {0}", this.DummyCashPresent.ToString());
}
private void UpdateState()
{
Atm.currentState = new NoCardState(this);
}
}
Now with this design we are atually achieving the same functionality that we were achieving in the earlier
example. This sure looks like more code but this approach is less error prone and using this approach the ATM
class
will need no modification. So if we need to add a new state then there will be minimal impact on code of
existing state classes. So if I need to add a state for NO_CASH
in this system, I just have to add a new concrete class
that and hook it with the system.
class NoCashState : ATMState
{
public NoCashState(ATMState state)
:this(state.DummyCashPresent, state.Atm)
{
}
public NoCashState(int amountRemaining, ATM atmBeingUsed)
{
this.Atm = atmBeingUsed;
this.DummyCashPresent = amountRemaining;
}
public override string GetNextScreen()
{
Console.WriteLine("ATM is EMPTY");
Console.ReadLine();
return string.Empty;
}
private void UpdateState()
{
}
}
And a minor modification in the UpdateState
function of the CardValidatedState
class.
private void UpdateState()
{
if (this.DummyCashPresent == 0)
{
Atm.currentState = new NoCashState(this);
}
else
{
Atm.currentState = new CashWithdrawnState(this);
}
}
Now let us test this system.
Before wrapping up let us look at the clas diagram of our application and compare it with the
class diagram of GoF.
Point of interest
In this article we tried to get an overviwe of the state pattern and when could it be used.
We also implemented as small application that contains a rudimentaty implementation of state pattern.
History
-
05 November 2012: First version.