Also known as Objects for States.
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
Use the State pattern in either of the following cases:
-
An object's behavior depends on its state, and it must change its behavior at run-time dependending on that sate.
-
Operations have large, multipart conditional statements that depend on the object's state. This state isusually represented by one or more enumerated constants. Often, several operations will contain this same conditional structure. The State pattern puts each branch of the conditional in a separate class. This lets you treat the object's state as an object in its own right that can vary independently from other objects.
-
Context
delegates state-specific requests to the currentConcreteState
object. -
A context may pass itself as an argument to the
State
object handling the request. This lets theState
object access the context if necessary. -
Context
is the primary interface for clients. Clients can configure a context withState
objects once a context is configured, its clients don't have to deal with theState
objects directly. -
Either
Context
or theConcreteState
subclasses can decide which state succeeds another and under what circumstances.
-
It localizes state-specific behavior and partitions behavior for different states. Because all state-specific code lives in a
State
subclass, new states and transitions can be added easily by defining new subclasses. This increases he number of classes and is less compact than a single class, but such distribution is actually good if there are many states, which would otherwise necessitate large conditional statements. -
It makes state transitions explicit. When an object defines its current state solely in terms of internal data values, its state transitions have no explicit representation, they only show up as assignments to some variables. Introducing separate objects for different states makes the transitions more explicit. Also,
State
objects can protect theContext
from inconsistent internal states, because state transitions are atomic from theContext
's perspective, they happen by rebinding one variable, not several. -
State objects can be shared. If
State
objects have no instance variables, that is the state they represent is encoded entirely in their type, then contexts can share aState
object. When states are shared in this way, they are essentially Flyweights with no intrinsic state, only behavior.
-
The Flyweight pattern explains when and how State objects can be shared.
-
State objects are often Singletons.
-
Who defines the state transitions? if the criteria are fiex, then they can be implemented entirely in the
Context
. It is generally more flexbiel and appropriate, however, to let theState
subclasses themselves specify their successor state and when to make the transition. This requires adding an interface to theContext
that letsState
objects set theContext
's current state explicitly. Decentralizing the transition logic in this way makes it easy to modify or extend the logic by defining newState
subclasses. A disadvantage of decentralization is that oneState
subclass will have knowledge of at least one other, which introduces implementation dependencies between subclasses. -
A table-based alternative. A state-driven code can be imposed by using a 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, you can 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 State pattern models state-specific behavior, whereas the table-driven approach focuses on defining state transitions.
-
Creating and destroying State objects. A common implementation trade-off worth worth considering is whethert to create
State
objects only when they are needed and destroy them thereafter versus 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 theState
objects store a lot of information. The second approach is better when state changes occur rapidly, in which case you want 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. -
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 languages. Exceptions include Self and other delegation-based languages that provide such a mechanism and hence support the State pattern directly. Objects in Self can delegate operations to other objects to achieve a form of dynamic inheritance. Changing the delegation target at run-time effectively changes the inheritance structure. This mechanism lets objects change their behavior and amounts to changing their class.
Consider a class TCPConnection
that represents a network connection. A TCPConnection
object can be in one of several different states: Established, Listening, Closed. When a TCPConnection
object receives requests from other objects, it responds differently depending on its current state. For example, the effect of an Open
request depends on whether the connection is in its Closed state or its Established state. The State pattern describes how TCPConnection
can exhibit different behavior in each state.
The key idea in this pattern is to introduce an abstract class called TCPState
to represent the states of the network connection. The TCPState
class declares an interface common to all classes that represent different operational states. Subclasses of TCPState
implement state-specific behavior. For example, the classes TCPEstablished
and TCPClosed
implement behavior particular to the Established and Closed states of TCPConnection
.
The class TCPConnection
maintains a state object (an instance of a subclass of TCPState
) that represents the current state of the TCP connection. The class TCPConnection
delegates all state-specific requests to this state object. TCPConnection
uses its TCPState
subclass instance to perform operations particular to the state of the connection.
Whenever the connection changes state, the TCPConnection
object changes the state object it uses. When the connection goes from Established to Closed, for example, TCPConnection
will replace its TCPEstablished
instance with a TCPClosed
instance.