Applied Design Patterns with Java
Behavioral :: State (305) {C ch 23}
Implementation
The following implementation issues are relevant to the
State
pattern:
- Who defines the state transitions? The State pattern
does not specify which participant defines the criteria for state transitions. If the criteria are fixed, then
they can be implemented entirely in the Context. It is generally more flexible and appropriate to let the State
subclasses specify their own successor state and when
to make the transition. This requires adding an interface to the Context that lets State objects set the Context's current state explicitly.
Decentralizing the transition logic in this way makes it easy to modify or extend the logic by defining new State
subclasses. A disadvantage of decentralization is that
one State subclass will have knowledge of at least one other, which introduces implementation dependencies
between subclasses.
- A table-based alternative. Some have advocated another way to impose structure on state-driven code:
using tables to map inputs to state transitions. For each state, a table maps every possible input to a succeeding
state. In effect, this approach converts conditional code (and virtual functions, in the case of the State
pattern) into a table look-up. The main advantage of tables is their regularity: change the transition criteria
by modifying data instead of changing program code. There are some disadvantages, however:
- A table look-up is often less efficient than a (virtual)
function call.
- Putting transition logic into a uniform, tabular format
makes the transition criteria less explicit and therefore harder to understand.
- It's usually difficult to add actions to accompany the
state transitions. The table-driven approach captures the states and their transitions, but it must be augmented
to perform arbitrary computation on each transition.
- The key difference between table-driven state machines
and the State pattern can be summarized: The State pattern models state-specific behavior, but the table-driven approach
focuses on defining state transitions.
- Creating and destroying State objects. A common
implementation trade-off worth considering is whether (1) to create State objects only when they are needed and destroy them thereafter versus
(2) creating them ahead of time and never destroying them. The first choice is preferable when the states that
will be entered aren't known at run-time, and contexts change state infrequently. This approach avoids creating
objects that won't be used, which is important if the State objects store a lot
of information. The second approach is better when state changes occur rapidly, so try to avoid destroying states,
because they may be needed again shortly. Instantiation costs are paid once up-front, and there are no destruction
costs at all. This approach might be inconvenient, though, because the Context must keep references to all states
that might be entered.
- Using dynamic inheritance. Changing the behavior for a particular request could be accomplished by changing
the object's class at run-time, but this is not possible in most object-oriented programming languages. There are
some laboratory languages that suport this, but neither Java or C++ allow this.
Related Patterns
The
Flyweight (195) pattern explains when and how State
objects can be shared.
State objects are often Singletons (127).
Catalog Behavioral Prev Next