How to implement state-dependent behavior

Doing the State pattern in Java

As someone who tends to think of objects as "data structures on steroids," it came as quite a shock when Netscape's Steve Abell pointed out that an object need not contain any values at all -- it can exist merely to provide behaviors, in the form of methods. This article shows how this concept, along with polymorphism (defined below), can be used to implement the archetypical State pattern in Java -- a pattern that finds many uses in a variety of different contexts.

Separating behaviors into disparate objects makes sense when the separation takes advantage of polymorphism. Polymorphism allows two objects to be treated identically, using the same methods, even though the objects implement these methods in quite different ways. It is this concept of "same appearance, different behavior" that gets the 0 word, polymorphism. (You can tell how important this concept is by the number of syllables the word has!)

Polymorphism works because the classes that implement the same method differently both derive, at some point, from a common superclass. A general program is written that operates on objects of the superclass type. The program is oblivious to the fact that what it thinks of as a superclass object is, in reality, an object that is a member of one of the subclass types. When the program invokes a method defined in the superclass, the method that gets called is actually the subclass method that overrides the superclass version.

Polymorphism allows very general programs to be written for a superclass, letting the subclasses take care of the details. An example of this is a drawing program that can write a loop that processes a list of graphic objects including lines, squares, and circles. It can work from the bottom most graphic object to the top, invoking the draw() method on each object in turn. The drawing program manages the sequence of drawing operations, which depends on which objects are "on top." Each object, in turn, is responsible for drawing itself. The program works on objects of type Graphic, knowing that every Graphic object always implements the draw() method. The subclasses Line, Square, and Circle, all derive from the superclass Graphic, and each overrides the draw() method in its own fashion. The program can then be extended by adding an Elipse or Rectangle class. As long as the new classes derive from Graphic, and as long as they implement the draw() method appropriately, the basic drawing program continues to work, unchanged.

(See Philip Bishop's JavaWorld article, "Polymorphism and Java," for more information on this subject.)

The State pattern, described in the book Design Patterns, by Gamma, Helm, Johnson, and Vlissides, Addison-Wesley, 1995 (see Resources for further book information), is an example of a tremendously useful software pattern that takes advantage of polymorphism. (The State pattern is just one of 23 useful patterns contained in that book.) The State pattern uses polymorphism to define different behaviors for different states of an object. It is a valuable pattern to master, because it can be used in practically any sizable application.

The following "precerpt," from The JBuilder Java Bible (due to be published this fall), describes how to implement the State pattern in Java.

Understanding the State pattern

The State pattern is useful when you have an object that can be in one of several states, with somewhat different behavior in each state. For example, when a car's RPMs are in the "power band," then the car's acceleration is much quicker than when the revolutions per minute are outside that range. The Car object might have a state variable that indicates "in the power band," "below the power band," or "above the power band." The acceleration characteristics of the car would then depend on its current state.

There are many cases in which an object's behavior depends on its current state. For example, when you're sleepy, it's hard to think. But when you're jazzed on coffee and chocolate, it can be easy! The examples go on and on. So it's worthwhile to have a readily usable State pattern to draw on.

The way the State pattern works is shown in the following diagram:

How the State pattern works

This diagram shows that the PowerBand class is an abstract class and the Car object contains a PowerBand object. The PowerBand class really acts as a kind of placeholder. Because it is abstract, it is not possible to create a PowerBand variable. Instead, the variable will hold one of the objects created from a subclass of PowerBand.

Each subclass then defines the behavior appropriate to that state. When the Car object receives a request to accelerate, it delegates the request to its state object. The kind of acceleration you get depends on the state the car is in at the time.

Writing a generic state class

Here is the code from State.java, stripped of its comments. In this section, we'll analyze this code to see how it works.

abstract class State { static State initialState; static final State state1 = new State1(); static final State state2 = new State2();

protected State() { if (initialState == null) initialState = this; }

abstract void stateExit(StateOwner owner); abstract void stateEnter(StateOwner owner); }

class State1 extends State { void stateExit(StateOwner owner) { }

void stateEnter(StateOwner owner) { }

}

class State2 extends State { void stateExit(StateOwner owner) { }

void stateEnter(StateOwner owner) { }

}

Analysis

To understand this bit of code, you'll need to take it apart and look at it, piece by piece.

State classes

Taking a high-level overview, you can see three classes:

abstract class State { ... }

class State1 extends State { ... }

class State2

extends State

{ ... }

The State class is an abstract class. It provides some basic behavior, but its only real purpose is to be extended to produce one or more "real" state classes. In this case, two state classes are defined, State1 and State2. To create additional states, you would copy one of these classes.

Static variables

The State class defines three variables:

  abstract class State {
    static State initialState;
    static final State state1 = new State1();
    static final State state2 = new State2();
    ...

The first variable is the default initial state. The first state that gets created will be stored in that variable, in case the StateOwner object wants to use it. (It doesn't have to.) The next two variables create the state objects using the classes State1 and State2.

Because these variables are defined as static, they belong to the State class. In other words, they are class variables, which means they belong to the class as a whole, rather than to individual objects. Without the static keyword, they would be instance variables, meaning that each object created using the class (each instance of the class) would have its own variable.

In general, using the static keyword means there is only one copy of the variable for the entire class, no matter how many objects are created using that class. Such variables generally are used for class-wide data, such as a count of the number of objects that are created using the class.

Protected constructors

The next significant part of the State class is the constructor, which saves the first state created as the default initial state. The important feature of the constructor is that it is defined as protected:

  protected State() {
    if (initialState == null) initialState = this;
  }

Because the constructor is protected, it cannot be used by other classes to create new objects. It can only be used by the State class and by those classes that extend the State class.

Since the State class is abstract, only the subclasses have access to the constructor, which is equivalent to making the subclass constructors private. In other words, the subclasses can create themselves using static variables, but they can never be used at run time to create additional objects.

In this case, the abstract State class creates the static variables state1 and state2. The protected constructor forms a "closed loop" with these static variables. The creation of the static variable state1 accesses the State1 constructor, which invokes the State constructor, which is protected. The final result is that the state variables can only be created from within the State class and its subclasses. They form a predetermined set that is defined at compile time.

Abstract methods

The last part of the State class defines two abstract methods, stateExit() and stateEnter():

    ...
    abstract void stateExit(StateOwner owner);
    abstract void stateEnter(StateOwner owner);
  }

Because these methods are defined as abstract, they have no method body. That means they must be implemented by the subclasses that extend the State class. That, fundamentally, is the reason for defining an abstract class in the first place -- to specify the kinds of methods the individual states will provide, and leave the implementation up to them.

The State class says that each state will have two methods, one for when you exit (or leave) a state, and one for when you enter (or arrive at) a state. Although you do not always need both methods, frequently it is helpful to have them. In multiple state systems, the combination of leaving one state and arriving at another can completely define the actions needed for a transition between the two states.

For example, consider the three states Accelerating, Slowing Down, and Stopping, as shown in the following diagram:

Three states: Acclerating, Slowing Down, Stopping

The action associated with slowing down might be to press lightly on the brake. The action associated with stopping might be to press firmly on the brake. When leaving the Accelerating state, the action in both cases would be to remove your foot from the accelerator. Only then would you take the action appropriate for slowing down or stopping.

Note: The exit action for the Slowing Down state could also be "remove foot from brake." That would allow a transition from slowing down to accelerating even though, for stopping, the foot would be put right back on the brake. In a real-time program, such inefficiency would not be acceptable. But in many applications, such a "take it away and put it right back" technique is a useful stratagem that produces a correct result with very simple code. Often, the performance penalty is insignificant compared with the clarity of the result.

Implemented methods

Finally, here is a class that defines one of the actual states:

class State1 extends State { void stateExit(StateOwner owner) { }

void stateEnter(StateOwner owner) { } }

This class defines the stateExit() and stateEnter() methods so they are ready to be filled in with the behaviors appropriate for the state.

Using the State class

Here is a sample of code for testing the State class:

  public class StateOwner {
    //Create the object and initialize its state
    public StateOwner() {
    this.stateVar = State.state1;
    stateVar.stateEnter(this);
    }
    // Set the new state
    public void setState(State newState) {
    stateVar.stateExit(this);
    this.stateVar = newState;
    stateVar.stateEnter(this);
    }
  private State stateVar;
  }

In this code, the constructor for the StateOwner class sets up the initial state and calls the entry method for that state.

The setState() method calls the exit method for the current state, sets the new state, and then calls the new state's entry method.

Finally, the private variable state stores the current state for the object.

(End of precept)

Related:
1 2 Page 1