λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
develop_kr/이둠

🎭 μƒνƒœ νŒ¨ν„΄(State Pattern): 객체의 μƒνƒœμ— λ”°λ₯Έ 행동 λ³€ν™”λ₯Ό μš°μ•„ν•˜κ²Œ κ΄€λ¦¬ν•˜κΈ°

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

πŸ” μƒνƒœ νŒ¨ν„΄μ΄λž€?

μƒνƒœ νŒ¨ν„΄(State Pattern)은 객체의 λ‚΄λΆ€ μƒνƒœκ°€ 변경될 λ•Œ 객체의 행동을 λ³€κ²½ν•  수 있게 ν•˜λŠ” ν–‰μœ„ λ””μžμΈ νŒ¨ν„΄μž…λ‹ˆλ‹€. 이 νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λ©΄ 객체가 마치 클래슀λ₯Ό λ°”κΎΌ κ²ƒμ²˜λŸΌ 보이게 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μƒνƒœ νŒ¨ν„΄μ€ μƒνƒœ λ¨Έμ‹ (State Machine)의 κ°œλ…μ„ 객체 μ§€ν–₯ λ°©μ‹μœΌλ‘œ κ΅¬ν˜„ν•œ κ²ƒμœΌλ‘œ, λ³΅μž‘ν•œ 쑰건문을 μ œκ±°ν•˜κ³  각 μƒνƒœλ³„ 행동을 독립적인 클래슀둜 μΊ‘μŠν™”ν•©λ‹ˆλ‹€.

πŸ—οΈ μƒνƒœ νŒ¨ν„΄μ˜ 핡심 ꡬ성 μš”μ†Œ

1. πŸ“‹ Context (μ»¨ν…μŠ€νŠΈ)

  • ν˜„μž¬ μƒνƒœλ₯Ό μ°Έμ‘°ν•˜λŠ” 클래슀
  • ν΄λΌμ΄μ–ΈνŠΈκ°€ 직접 μƒν˜Έμž‘μš©ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό 제곡
  • μƒνƒœ λ³€κ²½ μš”μ²­μ„ ν˜„μž¬ μƒνƒœ 객체에 μœ„μž„

2. 🎯 State (μƒνƒœ μΈν„°νŽ˜μ΄μŠ€)

  • λͺ¨λ“  ꡬ체적인 μƒνƒœ ν΄λž˜μŠ€κ°€ κ΅¬ν˜„ν•΄μ•Ό ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€
  • μƒνƒœλ³„λ‘œ λ‹€λ₯΄κ²Œ 처리될 λ©”μ„œλ“œλ“€μ„ μ •μ˜

3. πŸ”§ ConcreteState (ꡬ체적인 μƒνƒœ)

  • State μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜λŠ” ν΄λž˜μŠ€λ“€
  • 각 μƒνƒœμ—μ„œμ˜ ꡬ체적인 행동을 μ •μ˜
  • λ‹€μŒ μƒνƒœλ‘œμ˜ μ „ν™˜ λ‘œμ§μ„ 포함할 수 있음

✨ μƒνƒœ νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λŠ” 이유

πŸ‘ μž₯점

  • λ³΅μž‘ν•œ 쑰건문 제거: κ±°λŒ€ν•œ if-else λ¬Έμ΄λ‚˜ switch 문을 μ œκ±°ν•  수 μžˆμŠ΅λ‹ˆλ‹€
  • 단일 μ±…μž„ 원칙: 각 μƒνƒœλ³„ μ½”λ“œλ₯Ό 별도 클래슀둜 λΆ„λ¦¬ν•©λ‹ˆλ‹€
  • 개방-폐쇄 원칙: μƒˆλ‘œμš΄ μƒνƒœ μΆ”κ°€ μ‹œ κΈ°μ‘΄ μ½”λ“œ μˆ˜μ • 없이 ν™•μž₯ κ°€λŠ₯ν•©λ‹ˆλ‹€
  • μœ μ§€λ³΄μˆ˜μ„± ν–₯상: μƒνƒœλ³„ 둜직이 λͺ…ν™•ν•˜κ²Œ λΆ„λ¦¬λ˜μ–΄ μ΄ν•΄ν•˜κΈ° μ‰½μŠ΅λ‹ˆλ‹€

πŸ‘Ž 단점

  • 클래슀 수 증가: μƒνƒœλ§ˆλ‹€ λ³„λ„μ˜ 클래슀λ₯Ό 생성해야 ν•©λ‹ˆλ‹€
  • λ³΅μž‘λ„ 증가: κ°„λ‹¨ν•œ μƒνƒœ λ³€ν™”μ˜ 경우 μ˜€λ²„μ—”μ§€λ‹ˆμ–΄λ§μ΄ 될 수 μžˆμŠ΅λ‹ˆλ‹€

πŸš— Java 예제: μžλ™μ°¨ μƒνƒœ 관리

μžλ™μ°¨μ˜ μƒνƒœ(μ •μ§€, μ£Όν–‰, μ£Όμ°¨)에 따라 λ‹€λ₯Έ 행동을 ν•˜λŠ” μ˜ˆμ œμž…λ‹ˆλ‹€.

μƒνƒœ μΈν„°νŽ˜μ΄μŠ€ μ •μ˜

// μžλ™μ°¨ μƒνƒœ μΈν„°νŽ˜μ΄μŠ€
public interface CarState {
    void start(Car car);
    void drive(Car car);
    void park(Car car);
    void stop(Car car);
    String getStateName();
}

ꡬ체적인 μƒνƒœ ν΄λž˜μŠ€λ“€

// μ •μ§€ μƒνƒœ
public class StoppedState implements CarState {
    @Override
    public void start(Car car) {
        System.out.println("πŸš— 엔진을 μ‹œλ™ν•©λ‹ˆλ‹€.");
        car.setState(new DrivingState());
    }
    
    @Override
    public void drive(Car car) {
        System.out.println("❌ λ¨Όμ € μ‹œλ™μ„ κ±Έμ–΄μ£Όμ„Έμš”.");
    }
    
    @Override
    public void park(Car car) {
        System.out.println("❌ 이미 μ •μ§€λœ μƒνƒœμž…λ‹ˆλ‹€.");
    }
    
    @Override
    public void stop(Car car) {
        System.out.println("❌ 이미 μ •μ§€λœ μƒνƒœμž…λ‹ˆλ‹€.");
    }
    
    @Override
    public String getStateName() {
        return "μ •μ§€";
    }
}

// μ£Όν–‰ μƒνƒœ
public class DrivingState implements CarState {
    @Override
    public void start(Car car) {
        System.out.println("❌ 이미 μ‹œλ™μ΄ κ±Έλ €μžˆμŠ΅λ‹ˆλ‹€.");
    }
    
    @Override
    public void drive(Car car) {
        System.out.println("πŸƒ‍♂️ 달리고 μžˆμŠ΅λ‹ˆλ‹€! 뢀릉뢀릉~");
    }
    
    @Override
    public void park(Car car) {
        System.out.println("πŸ…ΏοΈ μ£Όμ°¨ν•©λ‹ˆλ‹€.");
        car.setState(new ParkedState());
    }
    
    @Override
    public void stop(Car car) {
        System.out.println("πŸ›‘ μ°¨λ₯Ό 멈μΆ₯λ‹ˆλ‹€.");
        car.setState(new StoppedState());
    }
    
    @Override
    public String getStateName() {
        return "μ£Όν–‰";
    }
}

// μ£Όμ°¨ μƒνƒœ
public class ParkedState implements CarState {
    @Override
    public void start(Car car) {
        System.out.println("πŸš— μ£Όμ°¨μ—μ„œ μΆœλ°œν•©λ‹ˆλ‹€.");
        car.setState(new DrivingState());
    }
    
    @Override
    public void drive(Car car) {
        System.out.println("❌ λ¨Όμ € μ£Όμ°¨μ—μ„œ λ‚˜κ°€μ£Όμ„Έμš”.");
    }
    
    @Override
    public void park(Car car) {
        System.out.println("❌ 이미 주차된 μƒνƒœμž…λ‹ˆλ‹€.");
    }
    
    @Override
    public void stop(Car car) {
        System.out.println("πŸ›‘ 엔진을 λ•λ‹ˆλ‹€.");
        car.setState(new StoppedState());
    }
    
    @Override
    public String getStateName() {
        return "μ£Όμ°¨";
    }
}

μ»¨ν…μŠ€νŠΈ 클래슀 (μžλ™μ°¨)

// μžλ™μ°¨ μ»¨ν…μŠ€νŠΈ 클래슀
public class Car {
    private CarState currentState;
    
    public Car() {
        this.currentState = new StoppedState();
    }
    
    public void setState(CarState state) {
        this.currentState = state;
        System.out.println("πŸ”„ μƒνƒœ λ³€κ²½: " + 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();
    }
}

μ‚¬μš© 예제

public class CarStateExample {
    public static void main(String[] args) {
        Car car = new Car();
        
        System.out.println("=== μžλ™μ°¨ μƒνƒœ νŒ¨ν„΄ ν…ŒμŠ€νŠΈ ===");
        System.out.println("ν˜„μž¬ μƒνƒœ: " + car.getCurrentState());
        
        // μ •μ§€ μƒνƒœμ—μ„œ μ‹œμž‘
        car.drive();  // μ‹œλ™μ„ λ¨Όμ € κ±Έμ–΄μ•Ό 함
        car.start();  // μ‹œλ™ κ±ΈκΈ°
        car.drive();  // μ£Όν–‰
        car.park();   // μ£Όμ°¨
        car.drive();  // μ£Όμ°¨ μƒνƒœμ—μ„œ μš΄μ „ μ‹œλ„
        car.stop();   // μ—”μ§„ 끄기
    }
}

🏠 TypeScript 예제: 슀마트 ν™ˆ μ‹œμŠ€ν…œ

μ§‘μ˜ μƒνƒœ(일반 λͺ¨λ“œ, μ™ΈμΆœ λͺ¨λ“œ, μ·¨μΉ¨ λͺ¨λ“œ)에 따라 λ‹€λ₯Έ 행동을 ν•˜λŠ” μ˜ˆμ œμž…λ‹ˆλ‹€.

μƒνƒœ μΈν„°νŽ˜μ΄μŠ€ μ •μ˜

// μ§‘ μƒνƒœ μΈν„°νŽ˜μ΄μŠ€
interface HomeState {
  enterHome(home: SmartHome): void;
  leaveHome(home: SmartHome): void;
  goToBed(home: SmartHome): void;
  wakeUp(home: SmartHome): void;
  getStateName(): string;
}

ꡬ체적인 μƒνƒœ ν΄λž˜μŠ€λ“€

// 일반 λͺ¨λ“œ (집에 μžˆλŠ” μƒνƒœ)
class HomeMode implements HomeState {
  enterHome(home: SmartHome): void {
    console.log('❌ 이미 집에 μžˆμŠ΅λ‹ˆλ‹€.');
  }
  
  leaveHome(home: SmartHome): void {
    console.log('πŸšͺ μ™ΈμΆœν•©λ‹ˆλ‹€. λ³΄μ•ˆ λͺ¨λ“œλ₯Ό ν™œμ„±ν™”ν•©λ‹ˆλ‹€.');
    console.log('πŸ’‘ λͺ¨λ“  λΆˆμ„ λ•λ‹ˆλ‹€.');
    console.log('πŸ”’ 문을 μž κΈ‰λ‹ˆλ‹€.');
    home.setState(new AwayMode());
  }
  
  goToBed(home: SmartHome): void {
    console.log('πŸŒ™ μ·¨μΉ¨ λͺ¨λ“œλ‘œ μ „ν™˜ν•©λ‹ˆλ‹€.');
    console.log('πŸ’‘ μ‘°λͺ…을 μ–΄λ‘‘κ²Œ μ‘°μ •ν•©λ‹ˆλ‹€.');
    console.log('πŸ”‡ μ•Œλ¦ΌμŒμ„ 무음으둜 μ„€μ •ν•©λ‹ˆλ‹€.');
    console.log('🌑️ μ˜¨λ„λ₯Ό μˆ˜λ©΄μ— μ ν•©ν•˜κ²Œ μ‘°μ •ν•©λ‹ˆλ‹€.');
    home.setState(new SleepMode());
  }
  
  wakeUp(home: SmartHome): void {
    console.log('❌ 이미 κΉ¨μ–΄μžˆλŠ” μƒνƒœμž…λ‹ˆλ‹€.');
  }
  
  getStateName(): string {
    return '일반 λͺ¨λ“œ';
  }
}

// μ™ΈμΆœ λͺ¨λ“œ
class AwayMode implements HomeState {
  enterHome(home: SmartHome): void {
    console.log('🏠 집에 λŒμ•„μ™”μŠ΅λ‹ˆλ‹€. ν™˜μ˜ν•©λ‹ˆλ‹€!');
    console.log('πŸ’‘ ν˜„κ΄€λ“±μ„ μΌ­λ‹ˆλ‹€.');
    console.log('πŸ”“ 문을 μ—΄κ³  λ³΄μ•ˆ λͺ¨λ“œλ₯Ό ν•΄μ œν•©λ‹ˆλ‹€.');
    console.log('🌑️ μ˜¨λ„λ₯Ό μΎŒμ ν•˜κ²Œ μ‘°μ •ν•©λ‹ˆλ‹€.');
    home.setState(new HomeMode());
  }
  
  leaveHome(home: SmartHome): void {
    console.log('❌ 이미 μ™ΈμΆœ μ€‘μž…λ‹ˆλ‹€.');
  }
  
  goToBed(home: SmartHome): void {
    console.log('❌ 집에 μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λ¨Όμ € 집에 λ“€μ–΄μ™€μ£Όμ„Έμš”.');
  }
  
  wakeUp(home: SmartHome): void {
    console.log('❌ 집에 μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.');
  }
  
  getStateName(): string {
    return 'μ™ΈμΆœ λͺ¨λ“œ';
  }
}

// μ·¨μΉ¨ λͺ¨λ“œ
class SleepMode implements HomeState {
  enterHome(home: SmartHome): void {
    console.log('❌ 이미 집에 μžˆμŠ΅λ‹ˆλ‹€.');
  }
  
  leaveHome(home: SmartHome): void {
    console.log('πŸŒ… 기상 ν›„ μ™ΈμΆœν•©λ‹ˆλ‹€.');
    console.log('πŸ’‘ λͺ¨λ“  λΆˆμ„ λ•λ‹ˆλ‹€.');
    console.log('πŸ”’ 문을 잠그고 λ³΄μ•ˆ λͺ¨λ“œλ₯Ό ν™œμ„±ν™”ν•©λ‹ˆλ‹€.');
    home.setState(new AwayMode());
  }
  
  goToBed(home: SmartHome): void {
    console.log('❌ 이미 μ·¨μΉ¨ λͺ¨λ“œμž…λ‹ˆλ‹€.');
  }
  
  wakeUp(home: SmartHome): void {
    console.log('β˜€οΈ 쒋은 μ•„μΉ¨μž…λ‹ˆλ‹€! 기상 λͺ¨λ“œλ‘œ μ „ν™˜ν•©λ‹ˆλ‹€.');
    console.log('πŸ’‘ μ‘°λͺ…을 밝게 μ‘°μ •ν•©λ‹ˆλ‹€.');
    console.log('πŸ”” μ•Œλ¦ΌμŒμ„ ν™œμ„±ν™”ν•©λ‹ˆλ‹€.');
    console.log('β˜• 컀피 머신을 μ€€λΉ„ν•©λ‹ˆλ‹€.');
    home.setState(new HomeMode());
  }
  
  getStateName(): string {
    return 'μ·¨μΉ¨ λͺ¨λ“œ';
  }
}

μ»¨ν…μŠ€νŠΈ 클래슀 (슀마트 ν™ˆ)

// 슀마트 ν™ˆ μ»¨ν…μŠ€νŠΈ 클래슀
class SmartHome {
  private currentState: HomeState;
  
  constructor() {
    this.currentState = new HomeMode();
  }
  
  setState(state: HomeState): void {
    this.currentState = state;
    console.log(`πŸ”„ μƒνƒœ λ³€κ²½: ${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();
  }
}

μ‚¬μš© 예제

// 슀마트 ν™ˆ ν…ŒμŠ€νŠΈ
function testSmartHome(): void {
  const home = new SmartHome();
  
  console.log('=== 슀마트 ν™ˆ μƒνƒœ νŒ¨ν„΄ ν…ŒμŠ€νŠΈ ===');
  console.log(`ν˜„μž¬ μƒνƒœ: ${home.getCurrentState()}`);
  
  // 일반 λͺ¨λ“œμ—μ„œ μ‹œμž‘
  home.goToBed();    // μ·¨μΉ¨ λͺ¨λ“œλ‘œ μ „ν™˜
  home.leaveHome();  // μ·¨μΉ¨ 쀑에 μ™ΈμΆœ μ‹œλ„
  home.wakeUp();     // 기상
  home.leaveHome();  // μ™ΈμΆœ
  home.goToBed();    // μ™ΈμΆœ 쀑에 μ·¨μΉ¨ μ‹œλ„
  home.enterHome();  // κ·€κ°€
  home.goToBed();    // λ‹€μ‹œ μ·¨μΉ¨
}

// ν…ŒμŠ€νŠΈ μ‹€ν–‰
testSmartHome();

πŸ€” μƒνƒœ νŒ¨ν„΄ vs μ „λž΅ νŒ¨ν„΄

λ§Žμ€ κ°œλ°œμžλ“€μ΄ μƒνƒœ νŒ¨ν„΄κ³Ό μ „λž΅ νŒ¨ν„΄μ„ ν˜Όλ™ν•˜λŠ”λ°, 두 νŒ¨ν„΄μ˜ 차이점은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

μƒνƒœ νŒ¨ν„΄ (State Pattern)

  • λͺ©μ : 객체의 λ‚΄λΆ€ μƒνƒœμ— 따라 행동을 λ³€κ²½
  • μƒνƒœ μ „ν™˜: μƒνƒœ 객체 μŠ€μŠ€λ‘œκ°€ λ‹€μŒ μƒνƒœλ₯Ό κ²°μ •
  • μ˜μ‘΄μ„±: μƒνƒœλ“€μ΄ μ„œλ‘œλ₯Ό μ•Œκ³  있음
  • μ‚¬μš© μ‹œμ : μƒνƒœ λ¨Έμ‹ , κ²Œμž„ 캐릭터, UI μ»΄ν¬λ„ŒνŠΈ λ“±

μ „λž΅ νŒ¨ν„΄ (Strategy Pattern)

  • λͺ©μ : μ•Œκ³ λ¦¬μ¦˜μ„ λŸ°νƒ€μž„μ— 선택
  • μ „λž΅ 선택: ν΄λΌμ΄μ–ΈνŠΈκ°€ μ „λž΅μ„ 선택
  • μ˜μ‘΄μ„±: μ „λž΅λ“€μ΄ μ„œλ‘œλ₯Ό λͺ¨λ¦„
  • μ‚¬μš© μ‹œμ : μ •λ ¬ μ•Œκ³ λ¦¬μ¦˜, 결제 방식, μ••μΆ• 방식 λ“±

🎯 μ–Έμ œ μƒνƒœ νŒ¨ν„΄μ„ μ‚¬μš©ν•΄μ•Ό ν• κΉŒ?

μƒνƒœ νŒ¨ν„΄μ€ λ‹€μŒκ³Ό 같은 μƒν™©μ—μ„œ μœ μš©ν•©λ‹ˆλ‹€:

  1. λ³΅μž‘ν•œ 쑰건문이 λ§Žμ€ 경우: 객체의 μƒνƒœμ— 따라 λ‹€λ₯Έ 행동을 ν•˜λŠ” κ±°λŒ€ν•œ if-else 문이 μžˆμ„ λ•Œ
  2. μƒνƒœ μ „ν™˜μ΄ λ³΅μž‘ν•œ 경우: μ—¬λŸ¬ μƒνƒœ κ°„μ˜ μ „ν™˜ κ·œμΉ™μ΄ λ³΅μž‘ν•  λ•Œ
  3. μƒνƒœλ³„ 행동이 λͺ…ν™•νžˆ κ΅¬λΆ„λ˜λŠ” 경우: 각 μƒνƒœμ—μ„œ μ™„μ „νžˆ λ‹€λ₯Έ 행동을 ν•΄μ•Ό ν•  λ•Œ
  4. μƒνƒœκ°€ 자주 μΆ”κ°€λ˜κ±°λ‚˜ λ³€κ²½λ˜λŠ” 경우: μƒˆλ‘œμš΄ μƒνƒœλ₯Ό μ‰½κ²Œ μΆ”κ°€ν•˜κ³  싢을 λ•Œ

πŸ“š κ²°λ‘ 

μƒνƒœ νŒ¨ν„΄μ€ λ³΅μž‘ν•œ μƒνƒœ 관리 λ‘œμ§μ„ κΉ”λ”ν•˜κ²Œ 정리할 수 μžˆλŠ” κ°•λ ₯ν•œ λ””μžμΈ νŒ¨ν„΄μž…λ‹ˆλ‹€. 특히 μƒνƒœ 머신이 ν•„μš”ν•œ μ‹œμŠ€ν…œμ—μ„œλŠ” ν•„μˆ˜μ μΈ νŒ¨ν„΄μ΄λΌκ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ κ°„λ‹¨ν•œ μƒνƒœ λ³€ν™”μ˜ κ²½μš°μ—λŠ” 였히렀 λ³΅μž‘λ„λ§Œ μ¦κ°€μ‹œν‚¬ 수 μžˆμœΌλ―€λ‘œ, ν”„λ‘œμ νŠΈμ˜ μš”κ΅¬μ‚¬ν•­κ³Ό λ³΅μž‘λ„λ₯Ό κ³ λ €ν•˜μ—¬ μ‹ μ€‘ν•˜κ²Œ μ μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

μƒνƒœ νŒ¨ν„΄μ„ 잘 ν™œμš©ν•˜λ©΄ μœ μ§€λ³΄μˆ˜κ°€ 쉽고 ν™•μž₯ κ°€λŠ₯ν•œ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€! πŸš€

728x90
λ°˜μ‘ν˜•

λŒ“κΈ€