State Pattern
Change object behavior based on internal state transitions
Pattern Overview
🎭 The State Pattern - Dynamic Behavior Change
Allows objects to alter behavior when internal state changes. The object appears to change its class dynamically!
- Core Problem Solved:
- Eliminate large conditional statements based on state
- Make state transitions explicit and manageable
- Allow different behavior for same methods based on state
- Encapsulate state-specific behavior in separate classes
- State Classes: Separate classes for each state with specific behavior
- State Machine: Centralized state transition logic and management
- Functional State: Functions representing different states and transitions
- Real-World Applications:
- Media player controls (play, pause, stop states)
- Game character behavior (idle, walking, jumping, attacking)
- UI component states (loading, error, success, idle)
- Network connection management (connected, disconnected, connecting)
- Workflow and approval processes
- Traffic light systems and automation
- Modern Usage Examples:
- React component state management with hooks
- Redux state machines and reducers
- Game AI behavior trees and state machines
- Finite state machines in Node.js applications
Examples:
Media player control
Input:
player.play() // when stoppedOutput:
Starting playback (state changes to Playing)Game character movement
Input:
character.handleInput('JUMP') // when walkingOutput:
Character state: Jumping (applies jump physics)Traffic light automation
Input:
trafficLight.next() // when redOutput:
Traffic light changed to: GreenConcepts
State TransitionsDynamic BehaviorFinite State MachineContext SwitchingState Encapsulation
Complexity Analysis
Time:O(1)
Space:O(1)
Implementation
media-player-states
Time: O(1) | Space: O(1)
// State interface defining behavior contract
interface MediaPlayerState {
play(player: MediaPlayer): void;
pause(player: MediaPlayer): void;
stop(player: MediaPlayer): void;
getStatus(): string;
}
// Context class managing current state
class MediaPlayer {
private currentState: MediaPlayerState;
private currentTrack: number = 0;
constructor() {
this.currentState = new StoppedState();
}
setState(state: MediaPlayerState): void {
this.currentState = state;
console.log(`State: ${state.getStatus()}`);
}
play(): void { this.currentState.play(this); }
pause(): void { this.currentState.pause(this); }
stop(): void { this.currentState.stop(this); }
}
// Concrete state implementations
class PlayingState implements MediaPlayerState {
play(player: MediaPlayer): void {
console.log('Already playing');
}
pause(player: MediaPlayer): void {
console.log('Pausing playback');
player.setState(new PausedState());
}
stop(player: MediaPlayer): void {
console.log('Stopping playback');
player.setState(new StoppedState());
}
getStatus(): string { return 'Playing'; }
}
class PausedState implements MediaPlayerState {
play(player: MediaPlayer): void {
console.log('Resuming playback');
player.setState(new PlayingState());
}
pause(player: MediaPlayer): void {
console.log('Already paused');
}
stop(player: MediaPlayer): void {
console.log('Stopping from pause');
player.setState(new StoppedState());
}
getStatus(): string { return 'Paused'; }
}
class StoppedState implements MediaPlayerState {
play(player: MediaPlayer): void {
console.log('Starting playback');
player.setState(new PlayingState());
}
pause(player: MediaPlayer): void {
console.log('Cannot pause when stopped');
}
stop(player: MediaPlayer): void {
console.log('Already stopped');
}
getStatus(): string { return 'Stopped'; }
}
// Usage
const player = new MediaPlayer();
player.play(); // Starting playback
player.pause(); // Pausing playback
player.play(); // Resuming playback
player.stop(); // Stopping playback