Chapter 10. The State Pattern: The State of Things

image with no caption

A little-known fact: the Strategy and State Patterns were twins separated at birth. As you know, the Strategy Pattern went on to create a wildly successful business around interchangeable algorithms. State, however, took the perhaps more noble path of helping objects to control their behavior by changing their internal state. He’s often overheard telling his object clients, “Just repeat after me: I’m good enough, I’m smart enough, and doggonit...”

Jawva Breakers

Java toasters are so ’90s. Today people are building Java into real devices, like gumball machines. That’s right, gumball machines have gone high tech; the major manufacturers have found that by putting CPUs into their machines, they can increase sales, monitor inventory over the network and measure customer satisfaction more accurately.

Note

At least that’s their story – we think they just got bored with the circa 1800’s technology and needed to find a way to make their jobs more exciting.

But these manufacturers are gumball machine experts, not software developers, and they’ve asked for your help:

image with no caption
image with no caption

Cubicle Conversation

image with no caption

Judy: This diagram looks like a state diagram.

Joe: Right, each of those circles is a state...

Judy: ... and each of the arrows is a state transition.

Frank: Slow down, you two, it’s been too long since I studied state diagrams. Can you remind me what they’re all about?

Judy: Sure, Frank. Look at the circles; those are states. “No Quarter” is probably the starting state for the gumball machine because it’s just sitting there waiting for you to put your quarter in. All states are just different configurations of the machine that behave in a certain way and need some action to take them to another state.

Joe: Right. See, to go to another state, you need to do something like put a quarter in the machine. See the arrow from “No Quarter” to “Has Quarter”?

Frank: Yes...

Joe: That just means that if the gumball machine is in the “No Quarter” state and you put a quarter in, it will change to the “Has Quarter” state. That’s the state transition.

Frank: Oh, I see! And if I’m in the “Has Quarter” state, I can turn the crank and change to the “Gumball Sold” state, or eject the quarter and change back to the “No Quarter” state.

Judy: You got it!

Frank: This doesn’t look too bad then. We’ve obviously got four states, and I think we also have four actions: “inserts quarter,” “ejects quarter,” “turns crank” and “dispense.” But... when we dispense, we test for zero or more gumballs in the “Gumball Sold” state, and then either go to the “Out of Gumballs” state or the “No Quarter” state. So we actually have five transitions from one state to another.

Judy: That test for zero or more gumballs also implies we’ve got to keep track of the number of gumballs too. Any time the machine gives you a gumball, it might be the last one, and if it is, we need to transition to the “Out of Gumballs” state.

Joe: Also, don’t forget that you could do nonsensical things, like try to eject the quarter when the gumball machine is in the “No Quarter” state, or insert two quarters.

Frank: Oh, I didn’t think of that; we’ll have to take care of those too.

Joe: For every possible action we’ll just have to check to see which state we’re in and act appropriately. We can do this! Let’s start mapping the state diagram to code...

State machines 101

How are we going to get from that state diagram to actual code? Here’s a quick introduction to implementing state machines:

  • ① First, gather up your states:

    image with no caption
  • ② Next, create an instance variable to hold the current state, and define values for each of the states:

    image with no caption
  • ③ Now we gather up all the actions that can happen in the system:

    image with no caption
  • ④ Now we create a class that acts as the state machine. For each action, we create a method that uses conditional statements to determine what behavior is appropriate in each state. For instance, for the insert quarter action, we might write a method like this:

    image with no caption
    image with no caption

With that quick review, let’s go implement the Gumball Machine!

Writing the code

It’s time to implement the Gumball Machine. We know we’re going to have an instance variable that holds the current state. From there, we just need to handle all the actions, behaviors and state transitions that can happen. For actions, we need to implement inserting a quarter, removing a quarter, turning the crank, and dispensing a gumball; we also have the empty Gumball Machine condition to implement.

image with no caption
image with no caption

In-house testing

That feels like a nice solid design using a well-thought-out methodology, doesn’t it? Let’s do a little in-house testing before we hand it off to Mighty Gumball to be loaded into their actual gumball machines. Here’s our test harness:

image with no caption

You knew it was coming... a change request!

Mighty Gumball, Inc., has loaded your code into their newest machine and their quality assurance experts are putting it through its paces. So far, everything’s looking great from their perspective.

In fact, things have gone so smoothly they’d like to take things to the next level...

image with no caption

The messy STATE of things...

Just because you’ve written your gumball machine using a well-thought-out methodology doesn’t mean it’s going to be easy to extend. In fact, when you go back and look at your code and think about what you’ll have to do to modify it, well...

image with no caption
image with no caption

Frank: You’re right about that! We need to refactor this code so that it’s easy to maintain and modify.

Judy: We really should try to localize the behavior for each state so that if we make changes to one state, we don’t run the risk of messing up the other code.

Frank: Right; in other words, follow that ol’ “encapsulate what varies” principle.

Judy: Exactly.

Frank: If we put each state’s behavior in its own class, then every state just implements its own actions.

Judy: Right. And maybe the Gumball Machine can just delegate to the state object that represents the current state.

Frank: Ah, you’re good: favor composition... more principles at work.

Judy: Cute. Well, I’m not 100% sure how this is going to work, but I think we’re on to something.

Frank: I wonder if this will make it easier to add new states?

Judy: I think so... We’ll still have to change code, but the changes will be much more limited in scope because adding a new state will mean we just have to add a new class and maybe change a few transitions here and there.

Frank: I like the sound of that. Let’s start hashing out this new design!

The new design

It looks like we’ve got a new plan: instead of maintaining our existing code, we’re going to rework it to encapsulate state objects in their own classes and then delegate to the current state when an action occurs.

We’re following our design principles here, so we should end up with a design that is easier to maintain down the road. Here’s how we’re going to do it:

  • ① First, we’re going to define a State interface that contains a method for every action in the Gumball Machine.

  • ② Then we’re going to implement a State class for every state of the machine. These classes will be responsible for the behavior of the machine when it is in the corresponding state.

  • ③ Finally, we’re going to get rid of all of our conditional code and instead delegate to the State class to do the work for us.

Not only are we following design principles, as you’ll see, we’re actually implementing the State Pattern. But we’ll get to all the official State Pattern stuff after we rework our code...

image with no caption

Defining the State interfaces and classes

First let’s create an interface for State, which all our states implement:

image with no caption

Then take each state in our design and encapsulate it in a class that implements the State interface.

Implementing our State classes

Time to implement a state: we know what behaviors we want; we just need to get it down in code. We’re going to closely follow the state machine code we wrote, but this time everything is broken out into different classes.

Let’s start with the NoQuarterState:

image with no caption
image with no caption

Reworking the Gumball Machine

Before we finish the State classes, we’re going to rework the Gumball Machine—that way you can see how it all fits together. We’ll start with the state-related instance variables and switch the code from using integers to using state objects:

image with no caption

Now, let’s look at the complete GumballMachine class...

image with no caption

Implementing more states

Now that you’re starting to get a feel for how the Gumball Machine and the states fit together, let’s implement the HasQuarterState and the SoldState classes...

image with no caption

Now, let’s check out the SoldState class...

image with no caption

Brain Power

Look back at the GumballMachine implementation. If the crank is turned and not successful (say the customer didn’t insert a quarter first), we call dispense anyway, even though it’s unnecessary. How might you fix this?

Let’s take a look at what we’ve done so far...

For starters, you now have a Gumball Machine implementation that is structurally quite different from your first version, and yet functionally it is exactly the same. By structurally changing the implemention, you’ve:

  • Localized the behavior of each state into its own class.

  • Removed all the troublesome if statements that would have been difficult to maintain.

  • Closed each state for modification, and yet left the Gumball Machine open to extension by adding new state classes (and we’ll do this in a second).

  • Created a code base and class structure that maps much more closely to the Mighty Gumball diagram and is easier to read and understand.

Now let’s look a little more at the functional aspect of what we did:

image with no caption
image with no caption

The State Pattern defined

Yes, it’s true, we just implemented the State Pattern! So now, let’s take a look at what it’s all about:

Note

The State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.

The first part of this description makes a lot of sense, right? Because the pattern encapsulates state into separate classes and delegates to the object representing the current state, we know that behavior changes along with the internal state. The Gumball Machine provides a good example: when the gumball machine is in the NoQuarterState and you insert a quarter, you get different behavior (the machine accepts the quarter) than if you insert a quarter when it’s in the HasQuarterState (the machine rejects the quarter).

What about the second part of the definition? What does it mean for an object to “appear to change its class”? Think about it from the perspective of a client: if an object you’re using can completely change its behavior, then it appears to you that the object is actually instantiated from another class. In reality, however, you know that we are using composition to give the appearance of a class change by simply referencing different state objects.

Okay, now it’s time to check out the State Pattern class diagram:

image with no caption
image with no caption

You’ve got a good eye! Yes, the class diagrams are essentially the same, but the two patterns differ in their intent.

With the State Pattern, we have a set of behaviors encapsulated in state objects; at any time the context is delegating to one of those states. Over time, the current state changes across the set of state objects to reflect the internal state of the context, so the context’s behavior changes over time as well. The client usually knows very little, if anything, about the state objects.

With Strategy, the client usually specifies the strategy object that the context is composed with. Now, while the pattern provides the flexibility to change the strategy object at runtime, often there is a strategy object that is most appropriate for a context object. For instance, in Chapter 1, some of our ducks were configured to fly with typical flying behavior (like mallard ducks), while others were configured with a fly behavior that kept them grounded (like rubber ducks and decoy ducks).

In general, think of the Strategy Pattern as a flexible alternative to subclassing; if you use inheritance to define the behavior of a class, then you’re stuck with that behavior even if you need to change it. With Strategy you can change the behavior by composing with a different object.

Think of the State Pattern as an alternative to putting lots of conditionals in your context; by encapsulating the behaviors within state objects, you can simply change the state object in context to change its behavior.

We still need to finish the Gumball 1 in 10 game

Remember, we’re not done yet. We’ve got a game to implement, but now that we’ve got the State Pattern implemented, it should be a breeze. First, we need to add a state to the GumballMachine class:

image with no caption

Now let’s implement the WinnerState class; it’s remarkably similar to the SoldState class:

image with no caption

Finishing the game

We’ve just got one more change to make: we need to implement the random chance game and add a transition to the WinnerState. We’re going to add both to the HasQuarterState since that is where the customer turns the crank:

image with no caption

Wow, that was pretty simple to implement! We just added a new state to the GumballMachine and then implemented it. All we had to do from there was to implement our chance game and transition to the correct state. It looks like our new code strategy is paying off...

Demo for the CEO of Mighty Gumball, Inc.

The CEO of Mighty Gumball has dropped by for a demo of your new gumball game code. Let’s hope those states are all in order! We’ll keep the demo short and sweet (the short attention span of CEOs is well documented), but hopefully long enough so that we’ll win at least once.

image with no caption
image with no caption
image with no caption
image with no caption

Sanity check...

Yes, the CEO of Mighty Gumball probably needs a sanity check, but that’s not what we’re talking about here. Let’s think through some aspects of the GumballMachine that we might want to shore up before we ship the gold version:

  • We’ve got a lot of duplicate code in the Sold and Winning states and we might want to clean those up. How would we do it? We could make State into an abstract class and build in some default behavior for the methods; after all, error messages like, “You already inserted a quarter,” aren’t going to be seen by the customer. So all “error response” behavior could be generic and inherited from the abstract State class.

    Note

    Dammit Jim, I’m a gumball machine, not a computer!

  • The dispense() method always gets called, even if the crank is turned when there is no quarter. While the machine operates correctly and doesn’t dispense unless it’s in the right state, we could easily fix this by having turnCrank() return a boolean, or by introducing exceptions. Which do you think is a better solution?

  • All of the intelligence for the state transitions is in the State classes. What problems might this cause? Would we want to move that logic into the Gumball Machine? What would be the advantages and disadvantages of that?

  • Will you be instantiating a lot of GumballMachine objects? If so, you may want to move the state instances into static instance variables and share them. What changes would this require to the GumballMachine and the States?

We almost forgot!

image with no caption
image with no caption

Tools for your Design Toolbox

It’s the end of another chapter; you’ve got enough patterns here to breeze through any job interview!

image with no caption
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.129.19