design pattern

Behavioral Design Patterns: State

As we approach the end of our thorough Guide to Software Design Patterns series we’ll be looking into the state design pattern. The state pattern allows you to programmatically change the behavior of a class based on changes made to the underlying state of said class. In this article we’ll look at both a real world example and a fully-functional C# code sample of the state design pattern, so let’s get to it!

In the Real World

The state pattern consists of three basic components:

  • Context – The base object that will contain a State object, indicating what state (and therefore what behavior) is currently implemented.
  • State – An interface or abstract class defining the basic characteristics (methods, properties, etc) of all ConcreteState objects.
  • ConcreteState – These individual classes implement the base State interface/abstract class. Each ConcreteState can implement its own logic and behavior, which will affect the Context instance when it is assigned to a particular ConcreteState.

As you can start to see, the purpose of the state design pattern is to allow Context objects to adjust their behavior solely because of the change of the current ConcreteState(s) that may be applied. While this sort of logic can be performed with traditional if-else control statements, it’s far cleaner to apply State objects to a Context object, so said Context doesn’t need to be aware of how the State logic is implemented.

In the real world, this can be seen all over, particularly in digital services and technologies. For example, consider making a purchase with your debit card, or depositing money into that same checking account. Behind the scenes, it’s likely that your account behaves as a Context object, with various ConcreteStates assigned to it, dependent on the characteristics of your account. If your bank has a minimum balance requirement before your account accrues interest, this change in behavior could be easily handled with a handful of ConcreteState objects. When your balance meets or exceeds the minimum balance threshold, the state of your account changes, and additional behaviors (such as applying interest) may be automatically put into action.

Full Code Sample

Below is the full code sample we’ll be using in this article. It can be copied and pasted if you’d like to play with the code yourself and see how everything works.

How It Works In Code

Our code sample continues with the bank account example, since just about everyone will have experience with the mechanics of depositing and withdrawing from a simple checking account. We start with the Context object, since this is the element upon which the entire state system is based. In this case, our Context object is the Account class:

As you can see, the Account class performs the basic functions: depositing money, withdrawing money, and accruing interest on the current balance. However, to accomplish these actions and change the behavior, we need the underlying AccountState object, which each Account tracks and changes for itself, as necessary:

AccountState is the base State object of our state pattern example, as it defines all the base properties and methods that each inherited ConcreteAccountState object will require. With the base AccountState abstract class in place, we can start implementing some specific ConcreteAccountStates, starting with the ZeroInterestAccountState:

Everything works as you might expect, but it’s worth noting the TryStateChange() method that is invoked inside every other major method call. This is where this specific ConcreteState object determines if the assigned Account.AccountState should be changed to a different state or not. In this case, if the balance falls below the LowerLimit of $0, we want to be sure the account is now considered overdrawn. On the other hand, if the balance exceeds the UpperLimit of $1,000, the account should start accruing interest.

Now, the TryStateChange() method and logic could be placed within the base AccountState object, but keeping it separated, and inside each individual ConcreteAccountState class, ensures that each class can have distinctly specific logic and behavior in the future, regardless of what other states may be doing. It’s also likely that in real-world code we’d opt to implement all the various calls to the TryStateChange() method in a more elegant manner, perhaps by linking all Withdraw, Deposit, and similar methods to an event, that could then ensure TryStateChange() logic is invoked when appropriate. But, for our purposes, this simple setup will suffice.

Next we have the InterestAccountState which, as we just saw, is applied when the balance exceeds $1,000:

For the most part, the behavior is the same here as with the ZeroInterestAccountState class, except calling AccrueInterest() actually calculates and adds the applied interest, based on the current interest rate, to the Account.Balance.

Finally, the OverdrawnAccountState class is for Accounts with a Balance below $0:

No interest can be accrued, nor can any withdrawals be made, so any attempt to do so issues a warning indicating a lack of funds.

To tie everything together and test it out we just create a new Account, then perform some deposits:

This produces two outputs, indicating the deposited values, along with the current Balance and AccountState:

Since we haven’t exceeded the $1,000 minimum to start earning interest, Alice's account remains in the ZeroInterestAccountState. However, let’s try depositing a few more times, so the Balance exceeds that limit:

Sure enough, the AccountState automatically changes to the InterestAccountState:

Now we can successfully accrue some interest on the account:

Finally, let’s try withdrawing far more than the account contains. We’ll start with $2,500, which succeeds because, even though the total Balance is only 2,420.25, an OverdrawnAccountState is allowed to go all the way down to the LowerLimit of -$1,000:

This puts Alice in the negative:

Now let’s try another withdrawal of over $1,000 on her already overdrawn account, and see what happens:

As expected, no action is taken and alert is given, indicating the severe lack of funds:

That’s the gist of it! I hope this article gave you a bit more information on what the state design pattern is, and how it can be easily implemented in your own code. For more information on all the other popular design patterns, head on over to our ongoing design pattern series here!