Using the STATE Design Pattern in Java
- Modeling States
- Refactoring to STATE
- Making States Constant
- Summary
The state of an object is a combination of the current values of its attributes. When you call a set- method, you typically change an object's state, and an object can change its own state as its methods execute.
In some cases, an object's state can be a prominent aspect of its behavior, such as when modeling transactions and machines. Logic that depends on the object's state may spread through many of the class's methods. To counter this spread, you can move state-specific behavior into a group of classes, with each class representing a different state. This lets you avoid having deep or complex if statements, relying instead on polymorphism to execute the right implementation of an operation. The intent of the STATE pattern is to distribute state-specific logic across classes that represent an object's state.
Modeling States
When you model an object whose state is important, you may find that you have a variable that tracks how the object should behave, depending on its state. This variable may appear in complex, cascading if statements that focus on how to react to the events that an object can experience. One problem with this approach to modeling state is that if statements can become complex. Another problem is that when you adjust how you model the state, you often have to adjust if statements in several methods. The STATE pattern offers a cleaner, simpler approach, using a distributed operation.
Consider the Oozinoz software that models the state of a carousel door. A carousel is a large, smart rack that accepts material through a doorway and stores the material according to a bar code ID on the material. The door operates with a single button. If the door is closed, clicking the button makes the door start opening. If you click again before the door opens fully, the door will begin closing. If you let the door open all the way, it will automatically begin closing after a 2-second timeout. You can prevent this by clicking again when the door is open. Figure 22.1 shows the states and transitions of the carousel's door.
Figure 22.1 A carousel door provides one-touch control with a single button that changes the door's state
Challenge 22.1
Suppose that you open the door and place a material bin in the doorway. Is there a way to make the door begin closing without waiting for it to time out?
A solution appears on page 415.
The diagram in Figure 22.1 is a UML state machine. Such diagrams can be much more informative than a corresponding textual description.
You can supply a Door_1 object that the carousel software will update with state changes in the carousel. (The underscore in the class name is a hint that we will soon refactor this class.) Figure 22.2 shows the Door_1 class.
Figure 22.2 The Door_1 class models a carousel door, relying on state change events sent by the carousel machine
The Door_1 class subclasses Observable so that clients, such as a GUI, can observe a door. The class definition declares its superclass and establishes the states that a door can enter:
package com.oozinoz.carousel; public class Door_1 extends Observable { public static final int CLOSED = -1; public static final int OPENING = -2; public static final int OPEN = -3; public static final int CLOSING = -4; public static final int STAYOPEN = -5; private int state = CLOSED; //... }
Not surprisingly, a textual description of the state of a door depends on the door's state:
public String status() { switch (state) { case OPENING : return "Opening"; case OPEN : return "Open"; case CLOSING : return "Closing"; case STAYOPEN : return "StayOpen"; default : return "Closed"; } }
When a user clicks the carousel's one-touch button, the carousel generates a call to a Door object's click() method. The Door_1 code for a state transition mimics the information in Figure 22.1:
public void click() { if (state == CLOSED) { setState(OPENING); } else if (state == OPENING || state == STAYOPEN) { setState(CLOSING); } else if (state == OPEN) { setState(STAYOPEN); } else if (state == CLOSING) { setState(OPENING); } }
The setState() method of the Door_1 class notifies observers of the door's change:
private void setState(int state) { this.state = state; setChanged(); notifyObservers(); }
Challenge 22.2
Write the code for the complete() and timeout() methods of the Door_1 class.
A solution appears on page 415.