State Pattern
The State pattern enables an object’s behavior to change when its internal state changes. State pattern is used to encapsulate varying behavior for the same routine based on the object’s state, allowing an object to change its behavior at runtime without resorting to large conditional statements.
Consider the music player app. The music player can be in any of the following states: stopped, paused, or playing. Depending on the player’s current condition, different behaviors are displayed by the buttons labeled “play,” “pause,” “stop,” and “next.” It can be difficult and challenging to maintain this implementation when there are a lot of conditional statements, especially when more states or actions are added.
Solution: The State pattern recommends developing distinct classes that implement the same interface or abstract class for each state of the music player, such as PlayingState
, PausedState
, and StoppedState
. The behavior is delegated to the current state object by the MusicPlayer class, which keeps track of it. The Music Player modifies the state object it refers to when the state changes.
Real-World Example
Consider a system of traffic lights. There are three possible states for the traffic light: red, green, or yellow. Each state requires a distinct set of actions:
- The traffic light flashes a red signal when it is in the red state.
- It shows green when it is in the Green state.
- It shows yellow when it is in the Yellow state.
The traffic light’s behavior varies in accordance with the precise sequence in which it changes states—from Red to Green to Yellow to Red.
Structure of State Pattern
The key components of the state pattern include:
- State Interface: This interface defines the methods for handling requests, which will be implemented differently by concrete state classes. For example, the State interface, which has
handleRequest()
and other methods. - Concrete State Classes: These classes carry out the implementation of the State interface as well as the methods specified within it. Every class represents a distinct object state. As in a traffic light system, RedState, GreenState, and YellowState.
- Context Class: This class has a state and uses the current state object to handle requests specific to that state. It keeps track of a reference to a state object that symbolizes its present condition.
- Client: The client interacts with the Context class and has the ability to request a state change.
The class diagram shows the Context class having a reference to the State interface and the Concrete State classes implementing the State interface. The interaction between the Context and the Concrete States encapsulates the state-dependent behavior.
Implementation of State Pattern
Let’s take the example of a Document class that can be in different states: Draft, Moderation, and Published.
// State Interface
interface State {
void publish(document)
void approve(document)
}
// Concrete States
class Draft implements State {
void publish(document) {
document.state = new Moderation()
}
void approve(document) {
// Draft cannot be approved directly
}
}
class Moderation implements State {
void publish(document) {
// Cannot publish from Moderation without approval
}
void approve(document) {
document.state = new Published()
}
}
class Published implements State {
void publish(document) {
// Already published
}
void approve(document) {
// Already approved
}
}
// Context Class
class Document {
State state = new Draft()
void publish() {
state.publish(this)
}
void approve() {
state.approve(this)
}
}
// Usage
document = new Document()
document.publish() // Changes state to Moderation
document.approve() // Changes state to Published
- State Interface: Defines methods for state-specific behaviors (
publish
,approve
). - Concrete States (Draft, Moderation, Published): Implement behaviors specific to a document’s state.
- Context (Document): Maintains a reference to the current state and delegates state-specific requests to this state object.
Implementation
package com.ashok.designpatterns.state;
/**
*
* @author ashok.mariyala
*
*/
interface State {
void publish(Document doc);
void approve(Document doc);
}
class Document {
private State state;
public Document() {
this.state = new Draft();
}
public void setState(State state) {
this.state = state;
}
public void publish() {
state.publish(this);
}
public void approve() {
state.approve(this);
}
}
class Draft implements State {
public void publish(Document doc) {
System.out.println("Publishing draft, moving to moderation.");
doc.setState(new Moderation());
}
public void approve(Document doc) {
System.out.println("Draft cannot be approved directly.");
}
}
class Moderation implements State {
public void publish(Document doc) {
System.out.println("Cannot publish from Moderation without approval.");
}
public void approve(Document doc) {
System.out.println("Approving moderation, moving to published.");
doc.setState(new Published());
}
}
class Published implements State {
public void publish(Document doc) {
System.out.println("Already published.");
}
public void approve(Document doc) {
System.out.println("Already approved.");
}
}
public class Solution {
public static void main(String[] args) {
Document doc = new Document();
doc.publish();
doc.approve();
}
}
The State pattern is an effective tool for managing state-dependent behavior in a clean and maintainable manner. It’s especially helpful when an object’s behavior varies greatly depending on its state. Understanding this pattern can significantly reduce the complexity of code structure in complex systems.
That’s all about the State Pattern. If you have any queries or feedback, please write us email at contact@waytoeasylearn.com. Enjoy learning, Enjoy Design patterns.!!