A State Idiom for Java

The "Gang of Four" design patterns book describes the State pattern and outlines an implementation. The idiom presented here takes a different approach that is based on implementing UML state diagram behavior.

The org.jmonde.state package

The org.jmonde.state package is used to help implement this idiom. This package provides support for nested states, transitions, history, and enter and exit actions.

This package does not provide for guard conditions, transition actions, or internal actions. These are provided by the idiom as described later in this document.

The classes in this package are not thread-safe. Instances of these classes should be encapsulated and it is the responsibility of the enclosing class to provide thread-safe access.

The State class

An instance of the State class is created for each state in the state diagram. The State class is not designed to be subclassed, it is meant to be used as is. However, for debugging, it is useful to override the toString() method to provide a meaningful name.

States can be nested by defining a parent/child relationship.

A child state can be defined to be the autoChild of its parent. This means that if a transition is explicitly made to a parent state that has an autoChild then, after entering the parent state, the autoChild state is automatically entered. You can bypass the autoChild mechanism of the parent by transitioning directly to a child state.

A parent state can also have history. A parent with history will dynamically change it's autoChild to the most-recently entered child.

Application-specific actions can be attached to a state. These actions are called each time a state is entered, exited, or becomes the current state. Actions implement the java.lang.Runnable interface.

The isActive method is called to determine if a state is active (i.e. is currently entered). If a child state is active then the parent is also active.

The StateMachine class

The StateMachine class is used to perform transitions. The StateMachine class is not designed to be subclassed, it is meant to be used as is. However, for debugging, it is useful to override the toString() method to provide a meaningful name.

When an instance of StateMachine is created it is given the initial state of the machine. The state machine is started by calling the enter method which causes the initial state to be entered.

When a state is entered it first recursively ensures that its parent state has been entered. The state then invokes its associated enter action. If the state has an autoChild then the autoChild is recursively entered. The last state to be entered during this process becomes the current state. A state can have an action associated with it which is invoked when the state becomes the current state.

Transitions are then performed in two steps. First, the leaveFor method is called with an argument that is the target state of the transition. The leaveFor method exits the current state by invoking the associated leave action. It then moves up the state nesting hierarchy exiting each state until a state is encountered that is an ancestor of the target state. The leaveFor method then returns. The transition is completed by calling the enter method which causes the target state of the transition to be entered. The target state of the transition is entered using the same process described above to enter the initial state of the machine (i.e. first the parent states are entered, then the target state itself, and finally any auto children).

The State Idiom

Example 1

StateExample1.java

Example 2

If you wanted the "Active" state to maintain its history then the state diagram would change to:

The following line would be added to the code that structures the state machine:

    myStateActive.setHistory(true);

No other changes are necessary. In particular, the code that handles the "activate" event does not change even though the transition is now more dynamic.

Example 3

Robert Martin has created a finite state machine compiler. Below is a state diagram of one of the subway turnstile examples that he uses. An implementation using this idiom is provided for comparison.

Turnstile.java

Conclusion

The implementation of a state diagram is straightforward using org.jmonde.state. Once it is implemented, however, it is not easy to "see" the state diagram in the code. This is because the different parts of the state diagram are spread throughout the code.

Download

The source is available under the GNU General Public License.

Release 1.1.0 Initial release