Object Oriented Design - State Pattern
The State pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes.
How it Works?
- The Context class holds a reference to a State object, representing its current state.
- The Context delegates state-specific behavior to the current State object.
- When an event or condition causes a state transition, the Context updates its State reference to a different ConcreteState object. This changes the behavior of the Context according to the new state.
- The State object can also trigger a state transition in the Context, leading to a switch to another ConcreteState.
Design an ATM System
The State design pattern is very suitable for an ATM machine's operations because the ATM's behavior changes depending on its state (e.g., idle, user authenticated, dispensing cash, etc.). In the State pattern, the state-specific behavior is encapsulated in separate classes, and the context class (ATM) changes its behavior by switching between these state objects.
State Interface:
Define a state interface that declares methods for all actions that can be performed on the ATM.
public interface ATMState {
void insertCard();
void ejectCard();
void enterPin();
void requestCash();
}
Concrete State Classes:
Implement the state interface for each state of the ATM.
public class IdleState implements ATMState {
private ATM atm;
@Override
public void insertCard() {
Syatem.out.println("Card Inserted");
context.setState(new CardInsertedState(atm));
}
// Other methods with default behavior for this state
}
public class CardInsertedState implements ATMState {
@Override
public void ejectCard() {
System.out.println("Card ejected");
context.setState(new IdleState(atm));
}
@Override
public void enterPin() {
System.out.println("Pin Entered");
context.setState(new HasPinState(atm));
}
}
ATM Context Class:
The ATM class holds references to all state objects and delegates state-specific requests to the current state.
public class ATMContext {
private ATMState idleState;
private ATMState cardInsertedState;
private ATMState hasPinState;
private ATMState currentState;
public ATMContext() {
idleState = new IdleState(this);
cardInsertedState = new CardInsertedState(this);
hasPinState = new HasPinState(this);
currentState = idleState;
}
public void insertCard() {
currentState.insertCard();
}
public void ejectCard() {
currentState.ejectCard();
}
public void enterPin() {
currentState.enterPin();
}
}
How does Decorator Pattern follow SOLID Principles?
1. Single Responsibility Principle (SRP)
- Each state class in the State pattern encapsulates all the behavior associated with a specific state of the context object.
- Each state class (
NoCardState
,HasCardState
, etc.) handles the behavior for that specific state only.
2. Open/Closed Principle (OCP)
- You can add new states that implement the
State
interface without changing the existing state classes or the context class. - New states can be added without changing existing states or the context.
3. Liskov Substitution Principle (LSP)
- The context class can reference any concrete state interchangeably through the
State
interface. - Each concrete state is interchangeable as they all implement the
ATMState
interface.
4. Interface Segregation Principle (ISP)
- The State interface should only declare methods that are relevant for the states. If the states require significantly different methods, it's better to segregate the interface into more specific ones so that a state class doesn't have to implement methods it doesn't use, thereby adhering to ISP.
- The
ATMState
interface does not enforce any unused methods on the concrete states.
5. Dependency Inversion Principle (DIP):
- The high-level module (the context) doesn't depend on low-level modules (the concrete states); both depend on abstractions.
- Both the high-level (
ATMContext
) and low-level (Concrete States
) modules depend on theATMState
abstraction.
Design Examples for State Pattern
1. Download Manager
- Use Case: Managing the state of file downloads (e.g., starting, in progress, paused, completed, failed).
- Application: Each download has a state object representing its current state. The state changes based on events like pause, resume, or network issues.
2. Parking Lot
- Use Case: Managing the state of parking spots (e.g., free, occupied).
- Application: Each parking spot can have a state indicating whether it's free or occupied, allowing the system to efficiently manage parking allocations.
3. Library Management
- Use Case: Tracking the status of books (e.g., available, checked out, reserved, lost).
- Application: Each book can have a state object that changes based on actions like checkout, return, or reservation.
4. Movie Ticket Booking
- Use Case: Handling the booking process (e.g., selecting seats, payment, confirmation).
- Application: The booking process can be managed as a state machine, where each step (selecting seats, entering details, making payment) is a different state.
5. Chess Game
- Use Case: Managing the phases of a chess game (e.g., setup, in-progress, check, checkmate, draw).
- Application: The game object changes its state based on the game's progress, affecting the rules and available moves.
6. E-Commerce
- Use Case: Managing the stages of an order lifecycle (e.g., placed, confirmed, shipped, delivered).
- Application: Each order can be in different states, with the system performing different actions as the order progresses through these states.
7. Ticket Resolution System for a Big E-commerce Company
- Use Case: Tracking the status of customer support tickets (e.g., open, in progress, on hold, resolved).
- Application: Tickets can have states that determine the current phase of resolution and the actions needed.