Skip to main content

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
🔍 Three Implementation Approaches:
  • 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 stopped
Output:
Starting playback (state changes to Playing)
Game character movement
Input:
character.handleInput('JUMP') // when walking
Output:
Character state: Jumping (applies jump physics)
Traffic light automation
Input:
trafficLight.next() // when red
Output:
Traffic light changed to: Green

Concepts

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