Skip to content

ステートパターン

公開日:November 17, 2024更新日:November 28, 2024
TypeScriptDesign pattern📄

ステートパターン

ステートパターン(State Pattern) は、オブジェクトの内部状態に応じて振る舞いを変えるためのデザインパターンです。このパターンでは、オブジェクトの状態を別々のクラスとして定義し、状態の変化に応じてオブジェクトの振る舞いを切り替えることができます。これにより、条件分岐を使った複雑なコードを避け、コードの保守性と可読性を向上させることが可能です。

ステートパターンは、オブジェクトが複数の状態を持ち、それに応じて異なる振る舞いをする必要がある場合に非常に有効です。例えば、オブジェクトが「準備中」「稼働中」「停止中」といった複数の状態を持つシステムなどで使用されます。

TypeScriptでのステートパターンの実装

以下に、TypeScriptでステートパターンを実装する例を紹介します。この例では、オーディオプレイヤーが「停止中」「再生中」「一時停止中」の3つの状態を持ち、それぞれの状態で異なる振る舞いをするシナリオを示します。

オーディオプレイヤーの例

typescript
// State インターフェース
interface State {
  play(): void;
  pause(): void;
  stop(): void;
}

// コンテキストクラス(オーディオプレイヤー)
class AudioPlayer {
  private state: State;

  constructor() {
    this.state = new StoppedState(this); // 初期状態は停止中
  }

  setState(state: State): void {
    this.state = state;
  }

  play(): void {
    this.state.play();
  }

  pause(): void {
    this.state.pause();
  }

  stop(): void {
    this.state.stop();
  }
}

// StoppedState クラス
class StoppedState implements State {
  private player: AudioPlayer;

  constructor(player: AudioPlayer) {
    this.player = player;
  }

  play(): void {
    console.log('Starting playback...');
    this.player.setState(new PlayingState(this.player));
  }

  pause(): void {
    console.log('Cannot pause. The player is already stopped.');
  }

  stop(): void {
    console.log('The player is already stopped.');
  }
}

// PlayingState クラス
class PlayingState implements State {
  private player: AudioPlayer;

  constructor(player: AudioPlayer) {
    this.player = player;
  }

  play(): void {
    console.log('Already playing.');
  }

  pause(): void {
    console.log('Pausing playback...');
    this.player.setState(new PausedState(this.player));
  }

  stop(): void {
    console.log('Stopping playback...');
    this.player.setState(new StoppedState(this.player));
  }
}

// PausedState クラス
class PausedState implements State {
  private player: AudioPlayer;

  constructor(player: AudioPlayer) {
    this.player = player;
  }

  play(): void {
    console.log('Resuming playback...');
    this.player.setState(new PlayingState(this.player));
  }

  pause(): void {
    console.log('Already paused.');
  }

  stop(): void {
    console.log('Stopping playback...');
    this.player.setState(new StoppedState(this.player));
  }
}

// 実際の使用例
const player = new AudioPlayer();
player.play();  // "Starting playback..."
player.pause(); // "Pausing playback..."
player.play();  // "Resuming playback..."
player.stop();  // "Stopping playback..."

解説

  1. Stateインターフェース

    • Stateインターフェースは、オーディオプレイヤーの状態ごとに必要なメソッド(playpausestop)を定義しています。これにより、各状態クラスが共通のインターフェースを持つことが保証されます。
  2. AudioPlayerクラス(コンテキスト)

    • AudioPlayerクラスは、オーディオプレイヤーのコンテキストを表すクラスで、現在の状態を保持しています。状態を変更するためのsetStateメソッドと、各操作(playpausestop)を実行するメソッドを持っています。
  3. StoppedState、PlayingState、PausedStateクラス(具体的な状態)

    • 各状態クラスはStateインターフェースを実装しており、AudioPlayerの状態に応じた振る舞いを提供します。たとえば、StoppedStateでは再生を開始することができ、PlayingStateでは一時停止や停止の操作が可能です。
  4. 実際の使用例

    • AudioPlayerインスタンスを作成し、playpausestopメソッドを呼び出すことで、状態に応じた振る舞いが実行されます。各メソッド呼び出しは、現在の状態によって異なる動作をします。

利点

  • 状態ごとの振る舞いの分離: 各状態ごとの振る舞いをクラスに分離することで、コードの可読性と保守性が向上します。
  • 条件分岐の削減: 状態ごとにクラスを分けることで、大量のifswitchによる条件分岐を避けることができます。
  • 拡張性の向上: 新しい状態を追加する場合でも、既存のコードに大きな変更を加えることなく新しいクラスを追加するだけで済みます。

使用例

  • メディアプレイヤー: オーディオやビデオプレイヤーの「再生」「一時停止」「停止」など、複数の状態を持つシステムで使用されます。
  • 文書編集アプリケーション: 文書の状態(編集中、保存済み、読み取り専用など)に応じて異なる操作を提供する場合に利用されます。
  • ゲーム開発: キャラクターの状態(立ち、歩き、ジャンプなど)に応じて異なる動作をする場合に使用されます。

まとめ

ステートパターンは、オブジェクトの状態に応じた振る舞いを管理するためのデザインパターンです。TypeScriptでの実装を通じて、状態ごとに異なる振る舞いをクラスに分離することで、コードの保守性と可読性を向上させることができました。

このパターンを利用することで、複雑な条件分岐を避け、オブジェクトの状態変化に応じた振る舞いをシンプルに管理することが可能になります。