λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
develop_en/theory

🎭 State Pattern: Elegantly Managing Object Behavior Changes Based on State

by JSsunday 2025. 7. 10.
728x90
λ°˜μ‘ν˜•

πŸ” What is the State Pattern?

The State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. Using this pattern, an object appears as if it has changed its class.

The State Pattern implements the concept of a state machine in an object-oriented way, eliminating complex conditional statements and encapsulating state-specific behavior into independent classes.

πŸ—οΈ Core Components of the State Pattern

1. πŸ“‹ Context

  • A class that maintains a reference to the current state
  • Provides the interface that clients interact with directly
  • Delegates state change requests to the current state object

2. 🎯 State (State Interface)

  • An interface that all concrete state classes must implement
  • Defines methods that will be handled differently by each state

3. πŸ”§ ConcreteState (Concrete States)

  • Classes that implement the State interface
  • Define specific behaviors for each state
  • May contain logic for transitioning to the next state

✨ Why Use the State Pattern?

πŸ‘ Advantages

  • Eliminates complex conditionals: Removes large if-else statements or switch statements
  • Single Responsibility Principle: Separates state-specific code into separate classes
  • Open-Closed Principle: New states can be added without modifying existing code
  • Improved maintainability: State-specific logic is clearly separated and easy to understand

πŸ‘Ž Disadvantages

  • Increased number of classes: Requires creating separate classes for each state
  • Increased complexity: Can be over-engineering for simple state changes

πŸš— Java Example: Car State Management

An example of a car that behaves differently based on its state (stopped, driving, parked).

State Interface Definition

// Car state interface
public interface CarState {
    void start(Car car);
    void drive(Car car);
    void park(Car car);
    void stop(Car car);
    String getStateName();
}

Concrete State Classes

// Stopped state
public class StoppedState implements CarState {
    @Override
    public void start(Car car) {
        System.out.println("πŸš— Starting the engine.");
        car.setState(new DrivingState());
    }
    
    @Override
    public void drive(Car car) {
        System.out.println("❌ Please start the engine first.");
    }
    
    @Override
    public void park(Car car) {
        System.out.println("❌ Already in stopped state.");
    }
    
    @Override
    public void stop(Car car) {
        System.out.println("❌ Already in stopped state.");
    }
    
    @Override
    public String getStateName() {
        return "Stopped";
    }
}

// Driving state
public class DrivingState implements CarState {
    @Override
    public void start(Car car) {
        System.out.println("❌ Engine is already running.");
    }
    
    @Override
    public void drive(Car car) {
        System.out.println("πŸƒ‍♂️ Driving! Vroom vroom~");
    }
    
    @Override
    public void park(Car car) {
        System.out.println("πŸ…ΏοΈ Parking the car.");
        car.setState(new ParkedState());
    }
    
    @Override
    public void stop(Car car) {
        System.out.println("πŸ›‘ Stopping the car.");
        car.setState(new StoppedState());
    }
    
    @Override
    public String getStateName() {
        return "Driving";
    }
}

// Parked state
public class ParkedState implements CarState {
    @Override
    public void start(Car car) {
        System.out.println("πŸš— Leaving the parking spot.");
        car.setState(new DrivingState());
    }
    
    @Override
    public void drive(Car car) {
        System.out.println("❌ Please leave the parking spot first.");
    }
    
    @Override
    public void park(Car car) {
        System.out.println("❌ Already parked.");
    }
    
    @Override
    public void stop(Car car) {
        System.out.println("πŸ›‘ Turning off the engine.");
        car.setState(new StoppedState());
    }
    
    @Override
    public String getStateName() {
        return "Parked";
    }
}

Context Class (Car)

// Car context class
public class Car {
    private CarState currentState;
    
    public Car() {
        this.currentState = new StoppedState();
    }
    
    public void setState(CarState state) {
        this.currentState = state;
        System.out.println("πŸ”„ State changed to: " + state.getStateName());
    }
    
    public void start() {
        currentState.start(this);
    }
    
    public void drive() {
        currentState.drive(this);
    }
    
    public void park() {
        currentState.park(this);
    }
    
    public void stop() {
        currentState.stop(this);
    }
    
    public String getCurrentState() {
        return currentState.getStateName();
    }
}

Usage Example

public class CarStateExample {
    public static void main(String[] args) {
        Car car = new Car();
        
        System.out.println("=== Car State Pattern Test ===");
        System.out.println("Current state: " + car.getCurrentState());
        
        // Starting from stopped state
        car.drive();  // Need to start first
        car.start();  // Start engine
        car.drive();  // Drive
        car.park();   // Park
        car.drive();  // Try to drive while parked
        car.stop();   // Turn off engine
    }
}

🏠 TypeScript Example: Smart Home System

An example of a house that behaves differently based on its state (home mode, away mode, sleep mode).

State Interface Definition

// Home state interface
interface HomeState {
  enterHome(home: SmartHome): void;
  leaveHome(home: SmartHome): void;
  goToBed(home: SmartHome): void;
  wakeUp(home: SmartHome): void;
  getStateName(): string;
}

Concrete State Classes

// Home mode (when at home)
class HomeMode implements HomeState {
  enterHome(home: SmartHome): void {
    console.log('❌ Already at home.');
  }
  
  leaveHome(home: SmartHome): void {
    console.log('πŸšͺ Leaving home. Activating security mode.');
    console.log('πŸ’‘ Turning off all lights.');
    console.log('πŸ”’ Locking the door.');
    home.setState(new AwayMode());
  }
  
  goToBed(home: SmartHome): void {
    console.log('πŸŒ™ Switching to sleep mode.');
    console.log('πŸ’‘ Dimming the lights.');
    console.log('πŸ”‡ Setting notifications to silent.');
    console.log('🌑️ Adjusting temperature for sleep.');
    home.setState(new SleepMode());
  }
  
  wakeUp(home: SmartHome): void {
    console.log('❌ Already awake.');
  }
  
  getStateName(): string {
    return 'Home Mode';
  }
}

// Away mode
class AwayMode implements HomeState {
  enterHome(home: SmartHome): void {
    console.log('🏠 Welcome home!');
    console.log('πŸ’‘ Turning on the entrance light.');
    console.log('πŸ”“ Unlocking the door and disabling security mode.');
    console.log('🌑️ Adjusting temperature to comfortable level.');
    home.setState(new HomeMode());
  }
  
  leaveHome(home: SmartHome): void {
    console.log('❌ Already away from home.');
  }
  
  goToBed(home: SmartHome): void {
    console.log('❌ Not at home. Please come home first.');
  }
  
  wakeUp(home: SmartHome): void {
    console.log('❌ Not at home.');
  }
  
  getStateName(): string {
    return 'Away Mode';
  }
}

// Sleep mode
class SleepMode implements HomeState {
  enterHome(home: SmartHome): void {
    console.log('❌ Already at home.');
  }
  
  leaveHome(home: SmartHome): void {
    console.log('πŸŒ… Waking up and leaving home.');
    console.log('πŸ’‘ Turning off all lights.');
    console.log('πŸ”’ Locking the door and activating security mode.');
    home.setState(new AwayMode());
  }
  
  goToBed(home: SmartHome): void {
    console.log('❌ Already in sleep mode.');
  }
  
  wakeUp(home: SmartHome): void {
    console.log('β˜€οΈ Good morning! Switching to wake mode.');
    console.log('πŸ’‘ Brightening the lights.');
    console.log('πŸ”” Enabling notifications.');
    console.log('β˜• Preparing the coffee machine.');
    home.setState(new HomeMode());
  }
  
  getStateName(): string {
    return 'Sleep Mode';
  }
}

Context Class (Smart Home)

// Smart home context class
class SmartHome {
  private currentState: HomeState;
  
  constructor() {
    this.currentState = new HomeMode();
  }
  
  setState(state: HomeState): void {
    this.currentState = state;
    console.log(`πŸ”„ State changed to: ${state.getStateName()}`);
  }
  
  enterHome(): void {
    this.currentState.enterHome(this);
  }
  
  leaveHome(): void {
    this.currentState.leaveHome(this);
  }
  
  goToBed(): void {
    this.currentState.goToBed(this);
  }
  
  wakeUp(): void {
    this.currentState.wakeUp(this);
  }
  
  getCurrentState(): string {
    return this.currentState.getStateName();
  }
}

Usage Example

// Smart home test
function testSmartHome(): void {
  const home = new SmartHome();
  
  console.log('=== Smart Home State Pattern Test ===');
  console.log(`Current state: ${home.getCurrentState()}`);
  
  // Starting from home mode
  home.goToBed();    // Switch to sleep mode
  home.leaveHome();  // Try to leave while sleeping
  home.wakeUp();     // Wake up
  home.leaveHome();  // Leave home
  home.goToBed();    // Try to sleep while away
  home.enterHome();  // Come home
  home.goToBed();    // Go to sleep again
}

// Run test
testSmartHome();

πŸ€” State Pattern vs Strategy Pattern

Many developers confuse the State Pattern with the Strategy Pattern. Here are the key differences:

State Pattern

  • Purpose: Change behavior based on object's internal state
  • State transition: State objects decide the next state themselves
  • Dependencies: States know about each other
  • Usage: State machines, game characters, UI components

Strategy Pattern

  • Purpose: Select algorithms at runtime
  • Strategy selection: Client selects the strategy
  • Dependencies: Strategies don't know about each other
  • Usage: Sorting algorithms, payment methods, compression methods

🎯 When to Use the State Pattern?

The State Pattern is useful in the following situations:

  1. Complex conditionals: When you have large if-else statements that behave differently based on object state
  2. Complex state transitions: When transition rules between states are complex
  3. Clear state-specific behavior: When each state requires completely different behavior
  4. Frequent state additions/changes: When you want to easily add new states

πŸ“š Conclusion

The State Pattern is a powerful design pattern that can cleanly organize complex state management logic. It's particularly essential in systems that require state machines.

However, for simple state changes, it might only increase complexity, so you should carefully consider the project's requirements and complexity before applying it.

When used effectively, the State Pattern helps you write maintainable and extensible code! πŸš€

728x90
λ°˜μ‘ν˜•

λŒ“κΈ€