Recent from talks
Nothing was collected or created yet.
State pattern
View on WikipediaThe state pattern is a behavioral software design pattern that allows an object to alter its behavior when its internal state changes. This pattern is close to the concept of finite-state machines. The state pattern can be interpreted as a strategy pattern, which is able to switch a strategy through invocations of methods defined in the pattern's interface.
The state pattern is used in computer programming to encapsulate varying behavior for the same object, based on its internal state. This can be a cleaner way for an object to change its behavior at runtime without resorting to conditional statements and thus improve maintainability.[1]: 395
Overview
[edit]
The state design pattern is one of twenty-three design patterns documented by the Gang of Four that describe how to solve recurring design problems. Such problems cover the design of flexible and reusable object-oriented software, such as objects that are easy to implement, change, test, and reuse.[3]
The state pattern is set to solve two main problems:[4]
- An object should change its behavior when its internal state changes.
- State-specific behavior should be defined independently. That is, adding new states should not affect the behavior of existing states.
Implementing state-specific behavior directly within a class is inflexible because it commits the class to a particular behavior and makes it impossible to add a new state or change the behavior of an existing state later, independently from the class, without changing the class. In this, the pattern describes two solutions:
- Define separate (state) objects that encapsulate state-specific behavior for each state. That is, define an interface (state) for performing state-specific behavior, and define classes that implement the interface for each state.
- A class delegates state-specific behavior to its current state object instead of implementing state-specific behavior directly.
This makes a class independent of how state-specific behavior is implemented. New states can be added by defining new state classes. A class can change its behavior at run-time by changing its current state object.
Structure
[edit]
In the accompanying Unified Modeling Language (UML) class diagram, the Context class doesn't implement state-specific behavior directly. Instead, Context refers to the State interface for performing state-specific behavior (state.handle()), which makes Context independent of how state-specific behavior is implemented. The ConcreteStateA and ConcreteStateB classes implement the State interface, that is, implement (encapsulate) the state-specific behavior for each state. The UML sequence diagram shows the run-time interactions:
The Context object delegates state-specific behavior to different State objects. First, Context calls handle(this) on its current (initial) state object (ConcreteStateA), which performs the operation and calls setState(ConcreteStateB) on Context to change context's current state to ConcreteStateB. The next time, Context again calls handle(this) on its current state object (ConcreteStateB), which performs the operation and changes context's current state to ConcreteStateA.
Example
[edit]This is a C++ example demonstrating the state pattern.
import std;
using std::shared_ptr;
// Abstract State
class FanState {
public:
virtual void handleButtonPress(class Fan& fan) const = 0;
virtual ~FanState() = default;
};
// Context
class Fan {
private:
shared_ptr<FanState> currentState;
public:
explicit Fan(shared_ptr<FanState> state):
currentState{state} {}
void setState(shared_ptr<FanState> state) noexcept {
currentState = state;
}
void pressButton() noexcept {
currentState->handleButtonPress(*this);
}
};
// Concrete States
class OffState : public FanState {
public:
void handleButtonPress(Fan& fan) const override {
std::println("Fan is OFF -> Switching to LOW speed.");
fan.setState(std::make_shared<LowSpeedState>());
}
};
class LowSpeedState : public FanState {
public:
void handleButtonPress(Fan& fan) const override {
std::println("Fan is on LOW speed -> Switching to HIGH speed.");
fan.setState(make_shared<HighSpeedState>());
}
};
class HighSpeedState : public FanState {
public:
void handleButtonPress(Fan& fan) const override {
std::println("Fan is on HIGH speed -> Turning OFF.");
fan.setState(make_shared<OffState>());
}
};
int main() {
Fan fan(std::make_shared<OffState>());
// Simulate pressing the button several times
fan.pressButton(); // OFF -> LOW
fan.pressButton(); // LOW -> HIGH
fan.pressButton(); // HIGH -> OFF
fan.pressButton(); // OFF -> LOW
return 0;
}
See also
[edit]References
[edit]- ^ a b Erich Gamma; Richard Helm; Ralph Johnson; John M. Vlissides (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. ISBN 0-201-63361-2.
- ^ "The State design pattern – Structure and Collaboration". w3sDesign.com. Retrieved 2017-08-12.
- ^ Erich Gamma; Richard Helm; Ralph Johnson; John Vlissides (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. pp. 305ff. ISBN 0-201-63361-2.
- ^ "The State design pattern - Problem, Solution, and Applicability". w3sDesign.com. Retrieved 2017-08-12.
State pattern
View on Grokipedia- A Context class that maintains a reference to the current State object and delegates state-dependent operations to it.
- An abstract State interface declaring methods for state-specific behaviors.
- Concrete State classes implementing the interface, each handling one state and potentially transitioning to another state by updating the context's state reference.[1][2]
Overview and Motivation
Definition and Purpose
The State pattern is one of the 23 behavioral design patterns cataloged by the Gang of Four in their influential 1994 book, Design Patterns: Elements of Reusable Object-Oriented Software.[4] As a behavioral pattern, it enables an object to appear to change its class by delegating state-specific behavior to separate state objects, allowing the object's behavior to vary dynamically as its internal state changes.[5] This delegation mechanism ensures that the object maintains a consistent interface while adapting its responses based on the current state, effectively encapsulating variations in behavior that would otherwise require scattered conditional logic. The pattern's primary intent is to allow an object to alter its behavior when its internal state changes, without relying on extensive conditional statements like if-else chains that proliferate in state-dependent code.[5] By encapsulating each possible state in its own class, the State pattern promotes the Open-Closed Principle, whereby the core context class remains closed to modification but open to extension through the addition of new state classes.[6] This approach localizes state-specific logic, reducing complexity and enhancing maintainability in systems where behavior evolves with state transitions. At its core, the State pattern involves a context object that maintains a reference to the current State object, which defines a common interface for all state-specific behaviors.[5] Concrete state subclasses implement this interface to handle operations appropriate to their particular state, and they may trigger transitions to other states by updating the context's reference, thereby enabling seamless behavioral shifts.[7]Problem It Solves
In object-oriented programming, objects frequently need to alter their behavior based on internal state changes, such as a document progressing from draft to published. A naive implementation relies on extensive conditional statements—like long if-else chains or switch blocks—within a single class to route actions according to the current state, which causes significant code bloat as the number of states grows. This approach complicates maintenance, as modifications to state logic must propagate across multiple methods, and it violates the Single Responsibility Principle by forcing one class to manage diverse behavioral concerns.[1][2] Consider a TCP connection object, which operates in distinct states including Listening, Established, Syn Sent, and Closed; in the Established state, methods like send() transmit data, whereas in Closed, they might raise errors or do nothing. Implementing these variations through scattered conditionals fragments the logic throughout the class, making the codebase harder to read and reason about.[1][2] Without addressing this, extending the system becomes problematic: introducing a new state demands updates to conditional blocks in numerous locations, heightening the chance of overlooked errors in state transitions. Additionally, the resulting tight coupling binds state-handling logic inextricably to the context class, rendering the design inflexible and prone to bugs during evolution.[1][2]Structure and Components
UML Class Diagram
The UML class diagram for the State pattern depicts the core structure of the design, illustrating how an object's behavior varies based on its internal state through delegation and polymorphism. At the center is the Context class, which serves as the primary interface for clients and maintains a reference to the current State object, represented as a composition relationship—a filled diamond arrow pointing from Context to State with a multiplicity of 1 on both ends, indicating that each Context instance owns exactly one State instance at a time.[2][8] The State is modeled as an abstract class or interface, featuring an abstract method such ashandle() (or request() in some notations), which defines the interface for state-specific operations and often takes the Context as a parameter to enable state transitions. ConcreteState subclasses, such as ConcreteStateA and ConcreteStateB, extend or implement State via generalization relationships—open triangle arrows pointing from each ConcreteState to State—where they provide concrete implementations of the handle() method tailored to their respective states. Additionally, a dependency relationship, shown as a dashed arrow from State to Context, highlights how state operations may rely on the Context for accessing shared data or initiating changes.[2][8]
This diagram emphasizes the pattern's encapsulation of state-dependent behavior, with the Context delegating requests to the current State without direct knowledge of concrete implementations, thereby promoting separation of concerns and extensibility for adding new states. Variations in the diagram may include optional methods in the State interface, such as changeState() or explicit transition operations, to model state shifts more directly, particularly when transitions are centralized rather than distributed across ConcreteStates.[2]
Key Elements and Responsibilities
The State pattern comprises three primary elements: the Context class, the State interface, and one or more ConcreteState classes, each with distinct responsibilities that enable an object's behavior to vary polymorphically based on its internal state.[9] The Context class serves as the primary interface for clients, encapsulating the object's overall behavior while maintaining a reference to the current ConcreteState instance. It delegates state-specific requests to this current state object and typically includes a method, such as setState() or changeState(), to allow transitions to a new state. This delegation ensures that the Context remains independent of concrete state details, promoting flexibility in state management.[9] The State interface defines a common protocol for all state-specific behaviors, typically declaring abstract methods like handle() that represent operations varying by state. It may also include optional methods for initiating state changes, though transitions are often handled within concrete implementations. By providing this uniform interface, the State ensures that all concrete states can be used interchangeably by the Context.[9] ConcreteState classes implement the State interface, each tailoring the behavior to a particular state of the Context—for instance, defining how a request is processed differently in "open" versus "closed" states. These classes handle state transitions by invoking the Context's setState() method with a new ConcreteState instance, and in complex scenarios, they may share a transition table to centralize logic for determining next states. ConcreteStates often maintain a reference back to the Context to access additional data or trigger changes, though this should be minimized to avoid circular dependencies where possible.[9] In terms of interactions, clients interact solely with the Context, which forwards requests to the current ConcreteState for execution, resulting in polymorphic behavior as different states alter the observed outcome. State transitions occur explicitly when a ConcreteState determines the need for change, replacing the Context's state reference and thereby shifting future behaviors without modifying the Context itself. This collaboration localizes state-dependent code and makes transitions transparent to clients.[9] Key design invariants include adherence to a single State interface across all ConcreteStates to guarantee substitutability, and careful management of transitions to prevent invalid states, often through atomic operations or guarded methods. While ConcreteStates may reference the Context for transitions, designs should avoid unnecessary mutual references to mitigate potential cycles in object graphs.[9]Implementation
Pseudocode
The State pattern's pseudocode provides a language-agnostic outline of its core structure, emphasizing how the Context delegates behavior to the current State and enables transitions between concrete states. This representation highlights the encapsulation of state-specific logic and the avoidance of conditional branching in the Context.[1][10] The Context maintains a reference to the current State and includes methods for updating the state and handling requests by delegating to the State.class [Context](/page/Context) {
State state;
function setState(State newState) {
state = newState;
}
function handleRequest() {
state.[handle](/page/Handle)(this);
}
}
class [Context](/page/Context) {
State state;
function setState(State newState) {
state = newState;
}
function handleRequest() {
state.[handle](/page/Handle)(this);
}
}
interface State {
function [handle](/page/Handle)([Context](/page/Context) context);
}
interface State {
function [handle](/page/Handle)([Context](/page/Context) context);
}
class ConcreteStateA implements State {
function handle(Context context) {
// Perform actions specific to State A
// ...
context.setState(new ConcreteStateB());
}
}
class ConcreteStateB implements State {
function handle(Context context) {
// Perform actions specific to State B
// ...
}
}
class ConcreteStateA implements State {
function handle(Context context) {
// Perform actions specific to State A
// ...
context.setState(new ConcreteStateB());
}
}
class ConcreteStateB implements State {
function handle(Context context) {
// Perform actions specific to State B
// ...
}
}
map< pair<State, Event>, State > transitionTable;
// Example initialization
transitionTable[(ConcreteStateA, EventX)] = ConcreteStateB;
transitionTable[(ConcreteStateB, EventY)] = ConcreteStateA;
// In handle method
function handle(Context context, Event event) {
State nextState = transitionTable[(this, event)];
if (nextState != null) {
context.setState(nextState);
}
}
map< pair<State, Event>, State > transitionTable;
// Example initialization
transitionTable[(ConcreteStateA, EventX)] = ConcreteStateB;
transitionTable[(ConcreteStateB, EventY)] = ConcreteStateA;
// In handle method
function handle(Context context, Event event) {
State nextState = transitionTable[(this, event)];
if (nextState != null) {
context.setState(nextState);
}
}
function setState(State newState) {
State currentState = this.state;
if (!isValidTransition(currentState, newState)) {
throw new InvalidStateTransitionError("Transition from " + currentState + " to " + newState + " is not allowed");
}
this.state = newState;
}
function setState(State newState) {
State currentState = this.state;
if (!isValidTransition(currentState, newState)) {
throw new InvalidStateTransitionError("Transition from " + currentState + " to " + newState + " is not allowed");
}
this.state = newState;
}
Example in Object-Oriented Language
The State pattern can be implemented in Java using an interface for the state abstraction and concrete classes for each state, allowing the context object to delegate behavior dynamically. The example below demonstrates a simple media player context with three states: StopState (initial state where no playback occurs), PlayState (active playback), and PauseState (temporary halt during playback). The player starts in the StopState and transitions between states via methods like play(), stop(), and pause(), with each state defining appropriate responses and transitions.[12]// State interface
interface State {
void handlePlay(MediaPlayer context);
void handleStop(MediaPlayer context);
void handlePause(MediaPlayer context);
}
// Concrete state: StopState
class StopState implements State {
@Override
public void handlePlay(MediaPlayer context) {
System.out.println("Starting playback.");
context.setState(new PlayState());
}
@Override
public void handleStop(MediaPlayer context) {
System.out.println("Already stopped.");
}
@Override
public void handlePause(MediaPlayer context) {
System.out.println("Cannot pause; not playing.");
}
}
// Concrete state: PlayState
class PlayState implements State {
@Override
public void handlePlay(MediaPlayer context) {
System.out.println("Already playing.");
}
@Override
public void handleStop(MediaPlayer context) {
System.out.println("Stopping playback.");
context.setState(new StopState());
}
@Override
public void handlePause(MediaPlayer context) {
System.out.println("Pausing playback.");
context.setState(new PauseState());
}
}
// Concrete state: PauseState
class PauseState implements State {
public void handlePlay(MediaPlayer context) {
System.out.println("Resuming playback.");
context.setState(new PlayState());
}
@Override
public void handleStop(MediaPlayer context) {
System.out.println("Stopping from paused.");
context.setState(new StopState());
}
@Override
public void handlePause(MediaPlayer context) {
System.out.println("Already paused.");
}
}
// Context: MediaPlayer
class MediaPlayer {
private State state;
public MediaPlayer() {
this.state = new StopState(); // Initial state setup
}
public void setState(State state) {
this.state = state;
}
public void play() {
state.handlePlay(this);
}
public void stop() {
state.handleStop(this);
}
public void pause() {
state.handlePause(this);
}
}
// Demo
public class MediaPlayerDemo {
public static void main(String[] args) {
MediaPlayer player = new MediaPlayer();
player.play(); // Output: Starting playback.
player.pause(); // Output: Pausing playback.
player.stop(); // Output: Stopping from paused.
player.play(); // Output: Starting playback.
}
}
// State interface
interface State {
void handlePlay(MediaPlayer context);
void handleStop(MediaPlayer context);
void handlePause(MediaPlayer context);
}
// Concrete state: StopState
class StopState implements State {
@Override
public void handlePlay(MediaPlayer context) {
System.out.println("Starting playback.");
context.setState(new PlayState());
}
@Override
public void handleStop(MediaPlayer context) {
System.out.println("Already stopped.");
}
@Override
public void handlePause(MediaPlayer context) {
System.out.println("Cannot pause; not playing.");
}
}
// Concrete state: PlayState
class PlayState implements State {
@Override
public void handlePlay(MediaPlayer context) {
System.out.println("Already playing.");
}
@Override
public void handleStop(MediaPlayer context) {
System.out.println("Stopping playback.");
context.setState(new StopState());
}
@Override
public void handlePause(MediaPlayer context) {
System.out.println("Pausing playback.");
context.setState(new PauseState());
}
}
// Concrete state: PauseState
class PauseState implements State {
public void handlePlay(MediaPlayer context) {
System.out.println("Resuming playback.");
context.setState(new PlayState());
}
@Override
public void handleStop(MediaPlayer context) {
System.out.println("Stopping from paused.");
context.setState(new StopState());
}
@Override
public void handlePause(MediaPlayer context) {
System.out.println("Already paused.");
}
}
// Context: MediaPlayer
class MediaPlayer {
private State state;
public MediaPlayer() {
this.state = new StopState(); // Initial state setup
}
public void setState(State state) {
this.state = state;
}
public void play() {
state.handlePlay(this);
}
public void stop() {
state.handleStop(this);
}
public void pause() {
state.handlePause(this);
}
}
// Demo
public class MediaPlayerDemo {
public static void main(String[] args) {
MediaPlayer player = new MediaPlayer();
player.play(); // Output: Starting playback.
player.pause(); // Output: Pausing playback.
player.stop(); // Output: Stopping from paused.
player.play(); // Output: Starting playback.
}
}
play() calls handlePlay on StopState, which prints "Starting playback." and sets the context to PlayState. Next, pause() delegates to PlayState's handlePause, printing "Pausing playback." and transitioning to PauseState. Then, stop() on PauseState prints "Stopping from paused." and sets StopState. Finally, another play() from StopState restarts the cycle, printing "Starting playback." and returning to PlayState. This demonstrates how state changes alter the object's behavior without modifying the context class itself.
In Java, the State interface enables polymorphism, with concrete states extending the common contract; the context holds a reference to the interface type, avoiding null states by initializing in the constructor (e.g., to StopState). For C++, an abstract base class with pure virtual methods could replace the interface, using virtual destructors for proper cleanup.
The code compiles with standard Java (e.g., javac MediaPlayerDemo.java) requiring no external libraries, producing a MediaPlayerDemo.class executable via java MediaPlayerDemo. At runtime, polymorphism is resolved through dynamic dispatch: the JVM's virtual method table (vtable) invokes the overridden method based on the actual object instance (e.g., PlayState's handlePlay), enabling seamless behavior variation as states change without conditional logic in the context.
