Bird
0
0
LLDsystem_design~7 mins

State pattern in LLD - System Design Guide

Choose your learning style9 modes available
Problem Statement
When an object changes its behavior based on its internal state, using many conditional statements (if-else or switch) makes the code complex and hard to maintain. This leads to bugs and difficulty in adding new states or behaviors.
Solution
The State pattern solves this by encapsulating each state into separate classes with their own behavior. The main object delegates state-specific behavior to the current state object, allowing easy state transitions and cleaner code without large conditional blocks.
Architecture
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│   Context     │──────▶│   State A     │       │   State B     │
│ (has current  │       │ (implements   │       │ (implements   │
│  state)       │       │  behavior)    │       │  behavior)    │
└───────────────┘       └───────────────┘       └───────────────┘
        │                      ▲                       ▲
        │                      │                       │
        └──────────────────────┴───────────────────────┘
                 State interface with common methods

This diagram shows the Context holding a reference to a State interface. Concrete State classes implement this interface and define behavior for each state. The Context delegates requests to the current State object.

Trade-offs
✓ Pros
Improves code organization by separating state-specific behavior into classes.
Makes adding new states easier without modifying existing code.
Reduces complex conditional logic and improves readability.
Supports dynamic state transitions at runtime.
✗ Cons
Increases the number of classes, which can add complexity for small systems.
Requires careful design to avoid state explosion if many states exist.
May introduce overhead due to delegation and object creation.
Use when an object has multiple states with distinct behaviors and state transitions are frequent or complex. Suitable for systems where maintainability and extensibility of state logic are important.
Avoid when the number of states is very small and unlikely to change, or when state behavior is simple enough to handle with straightforward conditional statements.
Real World Examples
Amazon
Amazon uses the State pattern in order processing to manage order states like Pending, Shipped, Delivered, and Cancelled, each with specific behaviors and transitions.
Netflix
Netflix applies the State pattern in video playback controls, managing states such as Playing, Paused, and Buffering with distinct user interactions.
Uber
Uber uses the State pattern to handle ride statuses like Requested, Accepted, In Progress, and Completed, enabling clear state transitions and behavior.
Code Example
The before code uses if-else to switch behavior based on a string state, which becomes hard to maintain as states grow. The after code defines a State interface and concrete State classes. The Context delegates behavior to the current State object, which changes the Context's state dynamically. This improves extensibility and readability.
LLD
### Before: Using conditionals
class Context:
    def __init__(self):
        self.state = 'A'

    def request(self):
        if self.state == 'A':
            print('Behavior for state A')
            self.state = 'B'
        elif self.state == 'B':
            print('Behavior for state B')
            self.state = 'A'

context = Context()
context.request()  # Behavior for state A
context.request()  # Behavior for state B


### After: Using State pattern
from abc import ABC, abstractmethod

class State(ABC):
    @abstractmethod
    def handle(self, context):
        pass

class StateA(State):
    def handle(self, context):
        print('Behavior for state A')
        context.state = StateB()

class StateB(State):
    def handle(self, context):
        print('Behavior for state B')
        context.state = StateA()

class Context:
    def __init__(self):
        self.state = StateA()

    def request(self):
        self.state.handle(self)

context = Context()
context.request()  # Behavior for state A
context.request()  # Behavior for state B
OutputSuccess
Alternatives
Strategy pattern
Strategy encapsulates interchangeable algorithms or behaviors but does not manage state transitions internally.
Use when: Choose Strategy when you need to switch algorithms dynamically without managing complex state transitions.
Simple conditional logic
Uses if-else or switch statements to handle states inline without separate classes.
Use when: Use simple conditionals when the number of states is very small and unlikely to grow.
Summary
The State pattern helps manage complex state-dependent behavior by encapsulating states into separate classes.
It eliminates large conditional statements and makes adding new states easier and safer.
This pattern improves code maintainability and supports dynamic state transitions at runtime.