When writing code for real systems we often need to build a state machine. That is, we write code that is based on a set of states and transitions. For instance, think about a stop light or even a virtual game. For better understand this. Let’s step through an example. How about a simple controller for a gumball machine? Let’s map out the states and transitions for a gumball machine.
Now to do that, we are going to assume first that the machine is full of gumballs. So, for our first state, that’s going to be the state where we are waiting on a quarter. We’ll call that no quarter. From there we could insert a quarter and transition to the has quarter state. From there I could eject a quarter if I wanted to and I’d go back to the no quarter state. But I could also turn the crank, and I’d go to the sold state. And from the sold state I dispense the gumball, and if machine saw its gumball in it I’d go back to the no quarter state. But if the machine is sold out, then I’d go back to the sold-out state. And I’ll stay there until the machine is refilled, and then I’d go back to the no quarter state. So, we’ve modelled this simple machine as a set of states and transitions.
How do we implement something like this? The common way is to implement the state machine as a set of state constants. So here I have a constant for each state sold out, no quarter, quarter and sold, and we also have a variable typically that holds the current state. In this case, we’re going to start out with no quarter.
Then what we need to do is for each transition write a method, for instance insert quarter. What that method does is it checks to see what state we’re in, and does the appropriate thing. If we are in the quarter state and I’ve inserted a quarter, we can’t do that because there’s already a quarter there. If the machine is sold out, we’re going to have an error because we have nothing to sell. And if we are in the sold state, we’re already in the process of getting a gumball in the crank so that is an error as well. And finally, if we are in the no quarter state, then we change the state to Has Quarter.
Let’s look at another one, turn crank. Here again if we are in the sold or sold out or no quarter state, then this is an error. Otherwise if the state is quarter, then we set the state to sold and call dispense to give the gumball. Notice that we end up writing a lot of code here to handle states that either don’t make sense or that cause a lot of problems. And they are bringing their problems with our program.
You’ll also notice that each transition end up writing a method, and within that methods for every state we have a conditional test. Now let’s think about this. We know what happens in every software project – change. Let’s say we get a feature request. For instance, we might want to give a promotion so every customer has 1 in 10 chance of winning a gumball. To accomplish this one thing, we can do is add another state called winner, and then from sold state we’ll transition to winner, and if there’s a winner we’ll dispense the extra gumball.
To accomplish that we’ll first have another state as a constant. But then what we’ll need to do for the new state, we’ll have to open every existing method and add a new conditional for that state, and then we need to add to new transitions as well. Each time we need to add a new state, we must go and change the existing code. In other words, we are violating the open/closed principle.
Let’s think about a different way of doing this. Let’s take all our states and turn them into objects. And we’ll have those objects implement a common state interface.
Now, let’s look at this interface. Notice that it has a method for each state transition, so if we add a new state, all we must do is create a new state object, then we just add new methods. We don’t have to touch existing code other than inserting some methods. And now that we have a state setup, we’re going to treat one of those states as the current state. In fact, we’ll use a state field to reference one of those fields, and further, we’ll change the current state over time as time changes.
Here’s how it works. Say we are in the no quarter state, then the state field will reference the no quarter object. When the quarter is inserted, then we’ll transition to the has quarter object, and the state field will reference that. And when the crank is turned, then we’ll switch to the sold state.
Now we are modelling each state as a class with a set of methods that know what to do for each transition. Adding a new state means implementing a new class that implements the state interface. Adding new transitions means implementing new methods across the classes, but without touching the existing code.
Now that we have a high level understanding of the state design, let’s look at the details of the pattern and we’ll see how to implement this in code.
We can use the state pattern to create a better design for the gumball machine. What we are going to do is implement a state class for each possible state of the gumball machine. Instead of using integers to represent states, we’ll be using state objects.
We’ll define a state interface that contains a method for every action in the gumball machine. Each new state class will implement this interface. So, each new state will be responsible for the behavior of the machine when it is in that state. For instance, when the machine is in the no quarter state, the methods insert quarter, eject quarter, turn crank, and dispense, will all be implemented to handle this state and handle the transition to a new state. So, if you insert a quarter, the insert quarter method will transition the state from the no quarter state to the has quarter state.
We’ll be able to get rid of all the conditional code in the gumball machine. And instead delegate to the current state and let the state handle the behavior.
Let’s look at the class diagram. The gumball machine is our context, it’s the class that manages all the various states. When a request is made on the context, the context delegates that request to a state. Every state implements a common interface, the state interface. So, no matter what kind of request is made on the context, the context can delegate that request to any of the states. That means, the context doesn’t need to know how the states are implemented, so we have a loose coupling between the context and the state – which is a good thing.
The definition of the state pattern says that the pattern allows an object to alter its behavior when its internal state changes, the object will appear to change its class.
Let’s break that down a bit. First, we have each state represented by a spate class. And each of those states implements its own behavior. That is, what it should do to any request made on that state. Because every state implements the same interface, the context can delegate all requests to whatever the current state is. The state and not the context is responsible for handling the requests. And for changing the state if necessary. For instance, in the gumball machine if we get a request to insert a quarter, the context just calls the insert quarter method on the current state. If the current state is the no quarter state, then the insert quarter method will handle the transition to the has quarter state. The states have different behaviors, so in effect the context changes behavior when it changes state. And that’s what the second part of the pattern definition means when it says the object will appear to change its class. Because the state transitions from one to another and because each state has a different behavior, to the context it appears that the current state is always changing behavior as if it were changing its class. But of course, each state implements a state interface, so it is all states.
By using the state pattern, we are adhering to several OO principles. We are encapsulating what varies by making each state responsible for its own behavior and separating that logic from the context, the gumball machine class. We are favoring composition over inheritance. Rather than creating subclasses of the gumball machine to handle different states and then overriding default behavior, we are instead composing the gumball machine with a set of different states. And putting the behavior of each state into its own class. There’s very little duplication of code in this application, because each state handles each request slightly differently. And the benefit of using composition is that the context can delegate all the requests to the current state without having to worry about which state that is. Finally, we are keeping all the classes in our design closed for modification but opened for extension. With this design, we can easily add new state with minimal amount of change required for any of the existing classes. It’s true that we would have to make some small changes to the context and few of the states to add a new state, but changes are few compared to the number of changes that would be needed in our original design.
We have quite a few classes, because we have a separate class for each of the states in the gumball machine as well as an interface for them to implement. Each of the states is very similar. Let’s look at the code for the gumball machine class. Each state in the machine is represented with a separate state object, which is initialized in the gumball machine constructor. We pass the gumball machine instance to the states, so they can use the getter and setter methods to access the current sate in the state variable.
We begin in the sold out state and if a non zero number of gumballs is passed to the gumball machine constructor, we set the count of gumballs and set the initial state to the no quarter state.The methods insert quarter, eject quarter and turn crank are simple. Each method simply delegates responsibility for handling requests to the current state. We’ve also added some getters and setters to the gumball machine. So that the states can access the current state as well as the other state objects.
Now let’s take a look at the individual states beginning with the state interface. The state interface is simple. It has just four methods that each of the states need to implement.
The no quarter state is where we’ll usually begin assuming that we initialized the gumball machine with some gumballs. This class implements the state interface and the constructor takes the instance of the gumball machine and stores it in the gumball machine field. The only valid action in the no quarter state is to insert a quarter. When you do that you see a message saying that you inserted a quarter and the state transitions to he has quarter state. The rest of the methods in the no quarter state prints error messages. If you do insert a quarter in the no quarter state, the gumball machine transitions to the has quarter state.
The has quarter state class also implements the state interface as all the state classes do. And takes a gumball machine as argument in the constructor. In this state you can do one of two things. You can eject a quarter or you can turn the crank. If you eject the quarter you see a message Quarter Returned and the current state is set back to No Quarter State. If you turned the crank, then you see a message turned and the current state is set to the sold state. And in the sold state what you want is to get a gumball.
To see how we’ll get a gumball, let’s take a look back at the gumball machine for a moment. When we turned the crank in the previous has quarter state, the turn crank method in the gumball machine was called, and this method does two things, it delegates to the current state’s turn crank method, but then it also calls the dispense method on the current state. The turn crank method changes the state from has quarter state to the sold state. So the dispense method is called on the sold state from the turn crank method in the gumball machine.
In the sold state method we release the gumball by first calling the release ball method in the gumball method. And then we check to see if there are any gumballs left, if there are, we set the state to the no quarter state. If there are no gumballs left we set the state to the sold out state. Any other action in the sold state just results in a message. In the sold out state there is no valid action, so we don’t do any state transitions in the methods of the state. The only way to get back to the no quarter state is to refill the machine which we do in the gumball machine.
The refill method in the gumball machine does this and sets the current state back to the no quarter state, so we can get gumballs again.
We can test the code by running the class gumball machine test drive.
In this class we create a new gumball machine with 5 gumballs. The first thing we do is print the state in the gumball machine and we can see that it has 5 gumballs and is waiting for a quarter. So we insert a quarter and turn the crank, and you can see that a gumball comes rolling out of the slot. Next we print the state of the machine again, and now we can see that it has got 4 gumballs and is waiting for a quarter. We can keep getting gumballs as long as there’s gumballs left in the machine.
By structuring the implementation of the gumball machine to use the state pattern we’ve localized the behavior of each state into its own class and have eliminated the conditional statements to manage the state. This makes the code easier to maintain and more flexible.
Introduction to Programming: Design Patterns. Lynda.com.