Open In App
Related Articles

State Design Pattern

Improve
Improve
Improve
Like Article
Like
Save Article
Save
Report issue
Report

The State design pattern is a behavioral software design pattern that allows an object to alter its behavior when its internal state changes. It achieves this by encapsulating the object’s behavior within different state objects, and the object itself dynamically switches between these state objects depending on its current state.

State-Design-Pattern255-(1)

What is a State Design Pattern?

The State Design Pattern is a behavioral design pattern that allows an object to change its behavior when its internal state changes. This pattern is particularly useful when an object’s behavior depends on its state, and the state can change during the object’s lifecycle.

This pattern focuses on managing state transitions and coordinating state-specific behaviors.

Components of State Design Pattern

1. Context

The Context is the class that contains the object whose behavior changes based on its internal state. It maintains a reference to the current state object that represents the current state of the Context. The Context provides an interface for clients to interact with and typically delegates state-specific behavior to the current state object.

2. State Interface or Base Class

The State interface or base class defines a common interface for all concrete state classes. This interface typically declares methods that represent the state-specific behavior that the Context can exhibit. It allows the Context to interact with state objects without knowing their concrete types.

3. Concrete States

Concrete state classes implement the State interface or extend the base class. Each concrete state class encapsulates the behavior associated with a specific state of the Context. These classes define how the Context behaves when it is in their respective states.

componentdiagramstate

Communication between the components

In the State design pattern, the communication between the components typically follows these steps:

Step1: Client Interaction

The client interacts with the Context object, either directly or indirectly, by invoking methods on it.

Step2 :Behavior Delegation

When the client triggers an action or requests a behavior from the Context, the Context delegates the responsibility to its current State object.

Step3 :State-specific Behavior Execution

The current State object receives the delegated request and executes the behavior associated with its particular state.

Step4 :Possible State Transition

Depending on the logic implemented within the State object or controlled by the Context, a state transition may occur.

Step5 :Update of Current State

If a state transition occurs, the Context updates its reference to the new State object, reflecting the change in its internal state.

Step6 :Continued Interaction

The client continues to interact with the Context as needed, and the process repeats, with behavior delegation to the appropriate State object based on the current state of the Context.

This communication flow ensures that the Context and State objects work together seamlessly to achieve dynamic behavior changes based on the internal state of the Context. The Context manages the state and delegates behavior to the current State object, while the State objects encapsulate state-specific behavior and handle transitions between states as necessary

Real-World Analogy of State Design Pattern

Imagine a traffic light as a robot. It has different moods like “Stop” (Red), “Get Ready” (Yellow), and “Go” (Green).

  • The robot changes its mood based on the time or if cars are waiting.
  • When it’s “Stop”, cars stop, and people can walk. When it’s “Get Ready”, it’s about to change. And when it’s “Go”, cars can drive.
  • This setup makes it easy to add new moods or change how the robot behaves without messing up everything else. So, it’s like having a robot traffic light that knows when to stop, get ready, or go!

Example of State Design Pattern

Imagine a vending machine that sells various products. The vending machine needs to manage different states such as ready to serve, waiting for product selection, processing payment, and handling out-of-stock situations. Design a system that models the behavior of this vending machine efficiently.

How State Design Pattern will help while building this system:

  • Modelling Different States:
    • The State design pattern allows us to model each state of the vending machine (e.g., ready, product selected, payment pending, out of stock) as a separate class.
    • This separation of concerns makes the codebase more organized and maintainable.
  • Encapsulation of State-specific Behavior:
    • Each state class encapsulates its specific behavior. For example, the ReadyState class handles the behavior when the machine is ready for product selection, while the PaymentPendingState class handles behavior related to processing payments.
    • This encapsulation helps in managing complex state-dependent logic and promotes better code readability.
  • Dynamic State Transition:
    • The State pattern facilitates dynamic state transitions. For instance, when a user selects a product, the vending machine transitions from the ReadyState to the ProductSelectedState, and further transitions occur based on the user’s actions.
    • This dynamic behavior allows the vending machine to adapt to different scenarios and handle state changes seamlessly.
  • Code Reusability:
    • By implementing states as separate classes, the State pattern promotes code reusability. States can be reused across different contexts or vending machine implementations without modification.
    • This reusability reduces code duplication and promotes a more modular and scalable design.
  • Maintainability and Flexibility:
    • As the vending machine requirements evolve or new states need to be added, the State pattern makes it easier to extend the system.
    • Modifications or additions to state-specific behavior can be made without affecting other parts of the codebase, leading to improved maintainability and flexibility.

User Interaction with the System

User interactions with the vending machine trigger state transitions. For example, when a user inserts money, the vending machine transitions from the “ReadyState” to the “PaymentPendingState.” Similarly, when a product is selected, the vending machine transitions to the “ProductSelectedState.” If a product is out of stock, the vending machine transitions to the “OutOfStockState.”

ClassDiagramState

Below is the code of above problem statement using Interpreter Pattern:

Let’s break down into the component wise code:

1. Context(VendingMachineContext)

The context is responsible for maintaining the current state of the vending machine and delegating state-specific behavior to the appropriate state object.

Java

public class VendingMachineContext {
    private VendingMachineState state;
 
    public void setState(VendingMachineState state) {
        this.state = state;
    }
 
    public void request() {
        state.handleRequest();
    }
}

                    

2. State Interface (VendingMachineState)

This interface defines the contract that all concrete state classes must implement. It typically contains a method or methods representing the behavior associated with each state of the vending machine.

Java

public interface VendingMachineState {
    void handleRequest();
}

                    

3. Concrete States (Specific Vending Machine States)

Concrete state classes represent specific states of the vending machine, such as “ReadyState,” “ProductSelectedState,” and “OutOfStockState.” Each concrete state class implements the behavior associated with its respective state, like allowing product selection, processing payment, or displaying an out-of-stock message.

Java

public class ReadyState implements VendingMachineState {
    @Override
    public void handleRequest() {
        System.out.println("Ready state: Please select a product.");
    }
}
 
public class ProductSelectedState implements VendingMachineState {
    @Override
    public void handleRequest() {
        System.out.println("Product selected state: Processing payment.");
    }
}
 
public class PaymentPendingState implements VendingMachineState {
    @Override
    public void handleRequest() {
        System.out.println("Payment pending state: Dispensing product.");
    }
}
 
public class OutOfStockState implements VendingMachineState {
    @Override
    public void handleRequest() {
        System.out.println("Out of stock state: Product unavailable. Please select another product.");
    }
}

                    

Complete code for the above example

Below is the complete code for the above example:

Java

interface VendingMachineState {
    void handleRequest();
}
 
class ReadyState implements VendingMachineState {
    @Override
    public void handleRequest() {
        System.out.println("Ready state: Please select a product.");
    }
}
 
class ProductSelectedState implements VendingMachineState {
    @Override
    public void handleRequest() {
        System.out.println("Product selected state: Processing payment.");
    }
}
 
class PaymentPendingState implements VendingMachineState {
    @Override
    public void handleRequest() {
        System.out.println("Payment pending state: Dispensing product.");
    }
}
 
class OutOfStockState implements VendingMachineState {
    @Override
    public void handleRequest() {
        System.out.println("Out of stock state: Product unavailable. Please select another product.");
    }
}
 
class VendingMachineContext {
    private VendingMachineState state;
 
    public void setState(VendingMachineState state) {
        this.state = state;
    }
 
    public void request() {
        state.handleRequest();
    }
}
 
public class Main {
    public static void main(String[] args) {
        // Create context
        VendingMachineContext vendingMachine = new VendingMachineContext();
 
        // Set initial state
        vendingMachine.setState(new ReadyState());
 
        // Request state change
        vendingMachine.request();
 
        // Change state
        vendingMachine.setState(new ProductSelectedState());
 
        // Request state change
        vendingMachine.request();
 
        // Change state
        vendingMachine.setState(new PaymentPendingState());
 
        // Request state change
        vendingMachine.request();
 
        // Change state
        vendingMachine.setState(new OutOfStockState());
 
        // Request state change
        vendingMachine.request();
    }
}

                    

Output

Ready state: Please select a product.
Product selected state: Processing payment.
Payment pending state: Dispensing product.
Out of stock state: Product unavailable. Please select another product.

                    

Communication between Components in the above example:

  • When a user interacts with the vending machine (Context), such as inserting money or selecting a product, the vending machine delegates the responsibility of handling the interaction to the current state object.
  • The current state object (e.g., “ReadyState” or “ProductSelectedState”) executes the behavior associated with that state, such as processing the payment or dispensing the selected product.
  • Depending on the outcome of the interaction and the logic implemented within the current state object, the vending machine may transition to a different state.
  • The process continues as the user interacts further with the vending machine, with behavior delegated to the appropriate state object based on the current state of the vending machine.

When to use the State Design Pattern

The State design pattern is beneficial when you encounter situations with objects whose behavior changes dynamically based on their internal state. Here are some key indicators:

  • Multiple states with distinct behaviors: If your object exists in several states (e.g., On/Off, Open/Closed, Started/Stopped), and each state dictates unique behaviors, the State pattern can encapsulate this logic effectively.
  • Complex conditional logic: When conditional statements (if-else or switch-case) become extensive and complex within your object, the State pattern helps organize and separate state-specific behavior into individual classes, enhancing readability and maintainability.
  • Frequent state changes: If your object transitions between states frequently, the State pattern provides a clear mechanism for managing these transitions and their associated actions.
  • Adding new states easily: If you anticipate adding new states in the future, the State pattern facilitates this by allowing you to create new state classes without affecting existing ones.

When not to use the State Design Pattern

While the State pattern offers advantages, it’s not always the best solution. Here are some cases where it might be overkill:

  • Few states with simple behavior: If your object has only a few simple states with minimal behavioral differences, the overhead of the State pattern outweighs its benefits. In such cases, simpler conditional logic within the object itself might suffice.
  • Performance-critical scenarios: The pattern can introduce additional object creation and method calls, potentially impacting performance. If performance is paramount, a different approach might be more suitable.
  • Over-engineering simple problems: Don’t apply the pattern just for the sake of using a design pattern. If your logic is clear and maintainable without it, stick with the simpler solution.

Ultimately, the decision to use the State pattern depends on the specific context and complexity of your problem. Consider the trade-offs between code organization, maintainability, performance, and development effort before applying it.



Last Updated : 13 Feb, 2024
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads