Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
Objects for States
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.
Use the State pattern in either of the following cases:
The State pattern has the following consequences:
An alternative is to use data values to define internal states and have Context operations check the data explicitly. But then we'd have look-alike conditional or case statements scattered throughout Context's implementation. Adding a new state could require changing several operations, which complicates maintenance.
The State pattern avoids this problem but might introduce another, because the pattern distributes behavior for different states across several State subclasses. This increases the 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.
Like long procedures, large conditional statements are undesirable.
They're monolithic and tend to make the code less explicit, which
in turn makes them difficult to modify and extend. The State pattern
offers a better way to structure state-specific code. The logic that
determines the state transitions doesn't reside in monolithic
if
or switch
statements but instead is partitioned
between the State subclasses. Encapsulating each state transition and
action in a class elevates the idea of an execution state to full
object status. That imposes structure on the code and makes its
intent clearer.
The State pattern raises a variety of implementation issues:
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.
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:
The key difference between table-driven state machines and the State pattern can be summed up like this: The State pattern models state-specific behavior, whereas the table-driven approach focuses on defining state transitions.
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, 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. This approach might be inconvenient, though, because the Context must keep references to all states that might be entered.
The following example gives the C++ code for the TCP connection example described in the Motivation section. This example is a simplified version of the TCP protocol; it doesn't describe the complete protocol or all the states of TCP connections.8
First, we define the class TCPConnection
, which provides an
interface for transmitting data and handles requests to change state.
class TCPOctetStream; class TCPState; class TCPConnection { public: TCPConnection(); void ActiveOpen(); void PassiveOpen(); void Close(); void Send(); void Acknowledge(); void Synchronize(); void ProcessOctet(TCPOctetStream*); private: friend class TCPState; void ChangeState(TCPState*); private: TCPState* _state; };
TCPConnection
keeps an instance of the TCPState
class in the _state
member variable. The class
TCPState
duplicates the state-changing interface of
TCPConnection
. Each TCPState
operation takes a
TCPConnection
instance as a parameter, letting
TCPState
access data from TCPConnection
and
change the connection's state.
class TCPState { public: virtual void Transmit(TCPConnection*, TCPOctetStream*); virtual void ActiveOpen(TCPConnection*); virtual void PassiveOpen(TCPConnection*); virtual void Close(TCPConnection*); virtual void Synchronize(TCPConnection*); virtual void Acknowledge(TCPConnection*); virtual void Send(TCPConnection*); protected: void ChangeState(TCPConnection*, TCPState*); };
TCPConnection
delegates all state-specific requests to its
TCPState
instance _state
.
TCPConnection
also provides an operation for changing this
variable to a new TCPState
. The constructor for
TCPConnection
initializes the object to the
TCPClosed
state (defined later).
TCPConnection::TCPConnection () { _state = TCPClosed::Instance(); } void TCPConnection::ChangeState (TCPState* s) { _state = s; } void TCPConnection::ActiveOpen () { _state->ActiveOpen(this); } void TCPConnection::PassiveOpen () { _state->PassiveOpen(this); } void TCPConnection::Close () { _state->Close(this); } void TCPConnection::Acknowledge () { _state->Acknowledge(this); } void TCPConnection::Synchronize () { _state->Synchronize(this); }
TCPState
implements default behavior for all requests
delegated to it. It can also change the state of a
TCPConnection
with the ChangeState
operation.
TCPState
is declared a friend of TCPConnection
to
give it privileged access to this operation.
void TCPState::Transmit (TCPConnection*, TCPOctetStream*) { } void TCPState::ActiveOpen (TCPConnection*) { } void TCPState::PassiveOpen (TCPConnection*) { } void TCPState::Close (TCPConnection*) { } void TCPState::Synchronize (TCPConnection*) { } void TCPState::ChangeState (TCPConnection* t, TCPState* s) { t->ChangeState(s); }
Subclasses of TCPState
implement state-specific behavior. A
TCP connection can be in many states: Established, Listening, Closed,
etc., and there's a subclass of TCPState
for each state.
We'll discuss three subclasses in detail: TCPEstablished
,
TCPListen
, and TCPClosed
.
class TCPEstablished : public TCPState { public: static TCPState* Instance(); virtual void Transmit(TCPConnection*, TCPOctetStream*); virtual void Close(TCPConnection*); }; class TCPListen : public TCPState { public: static TCPState* Instance(); virtual void Send(TCPConnection*); // ... }; class TCPClosed : public TCPState { public: static TCPState* Instance(); virtual void ActiveOpen(TCPConnection*); virtual void PassiveOpen(TCPConnection*); // ... };
TCPState
subclasses maintain no local state, so
they can be shared, and only one instance of each is required. The
unique instance of each TCPState
subclass is obtained by the
static Instance
operation.9
Each TCPState
subclass implements state-specific behavior
for valid requests in the state:
void TCPClosed::ActiveOpen (TCPConnection* t) { // send SYN, receive SYN, ACK, etc. ChangeState(t, TCPEstablished::Instance()); } void TCPClosed::PassiveOpen (TCPConnection* t) { ChangeState(t, TCPListen::Instance()); } void TCPEstablished::Close (TCPConnection* t) { // send FIN, receive ACK of FIN ChangeState(t, TCPListen::Instance()); } void TCPEstablished::Transmit ( TCPConnection* t, TCPOctetStream* o ) { t->ProcessOctet(o); } void TCPListen::Send (TCPConnection* t) { // send SYN, receive SYN, ACK, etc. ChangeState(t, TCPEstablished::Instance()); }
After performing state-specific work, these operations call the
ChangeState
operation to change the state of
the TCPConnection
. TCPConnection
itself doesn't
know a thing about the TCP connection protocol; it's the
TCPState
subclasses that define each state transition
and action in TCP.
Johnson and Zweig [JZ91] characterize the State pattern and its application to TCP connection protocols.
Most popular interactive drawing programs provide "tools" for performing operations by direct manipulation. For example, a line-drawing tool lets a user click and drag to create a new line. A selection tool lets the user select shapes. There's usually a palette of such tools to choose from. The user thinks of this activity as picking up a tool and wielding it, but in reality the editor's behavior changes with the current tool: When a drawing tool is active we create shapes; when the selection tool is active we select shapes; and so forth. We can use the State pattern to change the editor's behavior depending on the current tool.
We can define an abstract Tool class from which to define subclasses that implement tool-specific behavior. The drawing editor maintains a current Tool object and delegates requests to it. It replaces this object when the user chooses a new tool, causing the behavior of the drawing editor to change accordingly.
This technique is used in both the HotDraw [Joh92] and Unidraw [VL90] drawing editor frameworks. It allows clients to define new kinds of tools easily. In HotDraw, the DrawingController class forwards the requests to the current Tool object. In Unidraw, the corresponding classes are Viewer and Tool. The following class diagram sketches the Tool and DrawingController interfaces:
Coplien's Envelope-Letter idiom [Cop92] is related to State. Envelope-Letter is a technique for changing an object's class at run-time. The State pattern is more specific, focusing on how to deal with an object whose behavior depends on its state.
The Flyweight (195) pattern explains when and how State objects can be shared.
State objects are often Singletons (127).
TCPState
subclass a Singleton (see
Singleton (127)).