State is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
The State pattern is closely related to the concept of Finite State Machine.
Its main idea is that a program can be in one of the several states, which follow each other. The number of states and possible transitions between them is predefined and finite. Depending on the current state, the program behaves differently in response to the same events.
The similar approach can be applied to objects. For instance, the Document object can be in one of the three states:
Published. In each state, the
publish method will work in a different way:
- In the first case, it will move the document to moderation.
- In the second case, it will make the document public, but only if the current user is the administrator.
- In the third case, it will not do anything at all.
State machines are usually implemented with lots of conditional operators, such as
switch, that check current state and perform appropriate behavior. Even if you have just heard about state machines for the first time, you had probably implemented it at least once. Does the following code structure look familiar?
The biggest limitation of the state machine build with conditionals will show itself if you add some more states and state-dependent behaviors to the
Document class. Most methods will contain monstrous conditionals required for picking the proper behavior according to the state.
Such code is very difficult to maintain since any change to the transition logic requires double checking each conditional in every method.
The problem tends to get bigger the older a project is since it is quite difficult to predict all possible states and transitions beforehand at a design stage. Hence, a lean state machine built with a small set of conditionals can grow into a bloated mess over time.
The State pattern suggests to create new classes for all possible states of a context object and to extract the state-related behaviors into these classes.
The context will contain a reference to a state object that represents its current state. Instead of performing a behavior on its own, the context will delegate the execution to a state object.
To change the context's state, one would pass another state object to the context. But to make states interchangeable, all states classes must follow the common interface, and the context must communicate with its state object via that interface.
The described structure may look like the Strategy pattern, but there is one key difference. In the State pattern, the context, as wells as particular states, can initiate the transitions from one state to another.
The controls of your smartphone behave differently depending on the current state of the device:
- When the phone is unlocked, pressing buttons execute various functions.
- When the phone is locked, all buttons lead to the unlock screen.
- When the phone's charge is low, all buttons show the charging screen.
Context stores a reference to a Concrete State object and delegates it all state-related behaviors. Context works with a state via the common State interface. Context must expose a method for passing it a new state object.
State declares the common interface for all Concrete States. It declares methods that should make sense for all states. But each state will provide a different implementation.
Concrete State implement behaviors related to a particular state. Additional base classes may be created to avoid duplication of similar code across multiple states.
A state may store the back reference to its context object. This would not only give it the access to context data but also provide a way to initiate state transitions.
Both the Context and Concrete States may decide when to initiate a state transition and which would the following state be. To perform the transition, a new state object should be passed to the context.
In this example, the State pattern lets the same media player controls behave differently, depending on the current playback state. The main class of the player contains a reference to a state object, which performs most of the work for the player. Some actions may end-up replacing the state object with another, which changes the way the player reacts to the user interactions.
The State pattern lets the player change its behavior without other objects noticing. The transition can be performed either by the player itself or by a particular state object.
When you have an object that behaves differently depending on its current state. The number of states is big. The state-related code changes frequently.
The State pattern suggests isolating the code, related to a state inside a separate class. The original class called "context" should have a reference to one of such state objects. It would delegate the work to a linked state object. Such structure allows changing the context's behavior by supplying it with a different state object.
When a class is polluted with massive conditionals that alter method's behavior according to the current values of the class's fields.
The State pattern turns branches of the conditionals into methods within corresponding state classes. Then it relies on polymorphism to delegate the execution of the behavior to a linked state object.
When you have a lot of duplicate code across similar states and transitions of the conditional-based state machine.
The State pattern allows you to compose hierarchies of state classes and reduce duplication by moving common code into the base classes of the hierarchy.
How to Implement
Decide what class will play the role of Context. This could be an existing class, which already has the state-dependent code; or a new class, if the state-related code is distributed across multiple classes.
Create the State interface. In most cases, it will mirror the methods declared in the Context.
For each actual state, create a class implementing the State interface. Go over the Context methods and extract all code related to the particular state into the appropriate state class.
Add a reference field of the State type to the Context class and a public method to override its value.
Go over the method of the Context once again and replace leftovers of state conditionals with calls to the corresponding methods of the state object.
To switch the state of the Context, one must create an instance of a state class and pass it to the context. This can be done by the Context itself, or by the States, or by the Client. Note that the creator will become dependent on the concrete State class it instantiates.
Pros and Cons
- Eliminates state machine conditionals.
- Organizes the code related to particular states into separate classes.
- Simplifies the code of the context.
- Can be an overkill if a state machine has only a few states or rarely changes.
Relations with Other Patterns
State, Strategy, Bridge (and to some degree Adapter) have similar solution structures. They all share elements of the "handle/body" idiom. They differ in intent - that is, they solve different problems.
State can be considered as an extension of the Strategy pattern. Both patterns use the composition to change the behavior of the main object by delegating the work to the helper objects. The Strategy pattern makes these objects completely independent. However, the State pattern allows state objects to alter the current state of the context with another state, making them interdependent.