Ayden's journal

상태 패턴

상태 패턴(State Pattern)은 객체의 상태를 클래스로 분리하고, 상태가 변할 때 객체의 동작을 변경할 수 있도록 해 조건문을 사용하지 않고도 상태 전환을 관리할 수 있다. 이를 통해 코드의 복잡도를 낮추고 유지보수성을 높일 수 있으며, 새로운 상태를 추가할 때 기존 코드에 영향을 최소화할 수 있다. 또한, 각 상태를 독립적인 클래스로 분리함으로써 상태별 동작을 명확하게 정의할 수 있어 객체의 책임이 분리되고 코드의 가독성이 향상된다.

테이블 조회 방법은 상태 전이 규칙을 데이터로 관리하기 때문에 새로운 상태를 추가할 때 기존 로직을 수정하지 않아도 된다는 장점이 있지만, 상태별 동작이 복잡해질 경우 테이블만으로 이를 표현하기 어려울 수 있다. 반면, 상태 패턴은 각 상태를 개별 클래스로 구현하므로 상태별 동작을 직접 정의할 수 있어 코드의 응집도가 높아지고, 객체 지향적 설계를 통해 확장성을 확보할 수 있다.
그러나 상태 패턴은 클래스 수가 증가하여 코드가 복잡해질 수 있으며, 단순한 상태 전이를 구현하는 경우에는 오히려 과도한 구조가 될 수도 있다. 따라서, 상태 패턴은 상태별 동작이 복잡하고 확장 가능성이 높은 시스템에서 적합하며, 상태 수가 적고 단순한 경우에는 테이블 조회 방법이 더 효과적일 수 있다.

 

아래는 지금 읽고 있는 책 <디자인 패턴의 아름다움>에서 상태 패턴을 설명할 때 들었던 예시 코드를 자바에서 타입스크립트로 수정한 것이다. 자바 코드를 타입스크립트로 옮기는 행위만으로도 꽤 많은 걸 이해할 수 있었다.

enum State {
  SMALL,
  SUPER,
  CAPE,
  FIRE
}

interface IMario {
  getName(): State;
  obtainMushRoom(stateMachine: MarioStateMachine): void;
  obtainCape(stateMachine: MarioStateMachine): void;
  obtainFireFlower(stateMachine: MarioStateMachine): void;
  meetMonster(stateMachine: MarioStateMachine): void;
}

class SmallMario implements IMario {
  private static instance: SmallMario = new SmallMario();

  private constructor() {}

  public static getInstance(): SmallMario {
    return SmallMario.instance;
  }

  public getName(): State {
    return State.SMALL;
  }

  public obtainMushRoom(stateMachine: MarioStateMachine): void {
    stateMachine.setCurrentState(SuperMario.getInstance());
    stateMachine.setScore(stateMachine.getScore() + 100);
  }

  public obtainCape(stateMachine: MarioStateMachine): void {
    stateMachine.setCurrentState(CapeMario.getInstance());
    stateMachine.setScore(stateMachine.getScore() + 200);
  }

  public obtainFireFlower(stateMachine: MarioStateMachine): void {
    stateMachine.setCurrentState(FireMario.getInstance());
    stateMachine.setScore(stateMachine.getScore() + 300);
  }

  public meetMonster(stateMachine: MarioStateMachine): void {
    // 여기서는 아무 일도 하지 않으므로 빈 함수
  }
}

// SuperMario, CapeMario, FireMario 클래스 코드 생략
class SuperMario implements IMario {
  private static instance: SuperMario = new SuperMario();

  private constructor() {}

  public static getInstance(): SuperMario {
    return SuperMario.instance;
  }

  public getName(): State {
    return State.SUPER;
  }

  public obtainMushRoom(stateMachine: MarioStateMachine): void {
    // 이미 SuperMario 상태이므로 아무 일도 하지 않음
  }

  public obtainCape(stateMachine: MarioStateMachine): void {
    stateMachine.setCurrentState(CapeMario.getInstance());
    stateMachine.setScore(stateMachine.getScore() + 200);
  }

  public obtainFireFlower(stateMachine: MarioStateMachine): void {
    stateMachine.setCurrentState(FireMario.getInstance());
    stateMachine.setScore(stateMachine.getScore() + 300);
  }

  public meetMonster(stateMachine: MarioStateMachine): void {
    stateMachine.setCurrentState(SmallMario.getInstance());
    stateMachine.setScore(stateMachine.getScore() - 100);
  }
}

class CapeMario implements IMario {
  private static instance: CapeMario = new CapeMario();

  private constructor() {}

  public static getInstance(): CapeMario {
    return CapeMario.instance;
  }

  public getName(): State {
    return State.CAPE;
  }

  public obtainMushRoom(stateMachine: MarioStateMachine): void {
    // 이미 CapeMario 상태이므로 아무 일도 하지 않음
  }

  public obtainCape(stateMachine: MarioStateMachine): void {
    // 이미 CapeMario 상태이므로 아무 일도 하지 않음
  }

  public obtainFireFlower(stateMachine: MarioStateMachine): void {
    stateMachine.setCurrentState(FireMario.getInstance());
    stateMachine.setScore(stateMachine.getScore() + 300);
  }

  public meetMonster(stateMachine: MarioStateMachine): void {
    stateMachine.setCurrentState(SmallMario.getInstance());
    stateMachine.setScore(stateMachine.getScore() - 200);
  }
}

class FireMario implements IMario {
  private static instance: FireMario = new FireMario();

  private constructor() {}

  public static getInstance(): FireMario {
    return FireMario.instance;
  }

  public getName(): State {
    return State.FIRE;
  }

  public obtainMushRoom(stateMachine: MarioStateMachine): void {
    // 이미 FireMario 상태이므로 아무 일도 하지 않음
  }

  public obtainCape(stateMachine: MarioStateMachine): void {
    stateMachine.setCurrentState(CapeMario.getInstance());
    stateMachine.setScore(stateMachine.getScore() + 200);
  }

  public obtainFireFlower(stateMachine: MarioStateMachine): void {
    // 이미 FireMario 상태이므로 아무 일도 하지 않음
  }

  public meetMonster(stateMachine: MarioStateMachine): void {
    stateMachine.setCurrentState(SmallMario.getInstance());
    stateMachine.setScore(stateMachine.getScore() - 300);
  }
}

class MarioStateMachine {
  private score: number;
  private currentState: IMario;

  constructor() {
    this.score = 0;
    this.currentState = SmallMario.getInstance();
  }

  public obtainMushRoom(): void {
    this.currentState.obtainMushRoom(this);
  }

  public obtainCape(): void {
    this.currentState.obtainCape(this);
  }

  public obtainFireFlower(): void {
    this.currentState.obtainFireFlower(this);
  }

  public meetMonster(): void {
    this.currentState.meetMonster(this);
  }

  public getScore(): number {
    return this.score;
  }

  public getCurrentState(): State {
    return this.currentState.getName();
  }

  public setScore(score: number): void {
    this.score = score;
  }

  public setCurrentState(currentState: IMario): void {
    this.currentState = currentState;
  }
}

// 사용 예제
class Application {
  public static main(): void {
    const stateMachine = new MarioStateMachine();
    stateMachine.obtainMushRoom();
    stateMachine.obtainCape();
    stateMachine.obtainFireFlower();
    stateMachine.meetMonster();
    console.log(`Score: ${stateMachine.getScore()}`);
    console.log(`Current State: ${State[stateMachine.getCurrentState()]}`);
  }
}

Application.main();

 

블로그의 정보

Ayden's journal

Beard Weard Ayden

활동하기