State パターン

オブジェクトの内部状態に応じて振る舞いを切り替え、状態遷移を明示的にモデル化するデザインパターン

設計パターンフロントエンド

State パターンとは

State パターンは、オブジェクトの振る舞いを内部状態に応じて切り替える GoF デザインパターンである。if-else や switch 文で状態を判定する代わりに、各状態をクラスとして表現し、状態遷移を明示的にモデル化する。

Before / After

// ❌ switch 文: 状態が増えるたびに全メソッドに分岐を追加
class Order {
  status: 'draft' | 'pending' | 'paid' | 'shipped' | 'cancelled';

  confirm() {
    switch (this.status) {
      case 'draft': this.status = 'pending'; break;
      case 'pending': throw new Error('Already confirmed');
      case 'paid': throw new Error('Already paid');
      // ... 状態が増えるたびに分岐が増える
    }
  }

  pay() {
    switch (this.status) {
      case 'pending': this.status = 'paid'; break;
      // ... 同じパターンの繰り返し
    }
  }
}

// ✅ State パターン: 各状態がクラス、遷移が明示的
interface OrderState {
  confirm(order: Order): void;
  pay(order: Order): void;
  ship(order: Order): void;
  cancel(order: Order): void;
}

class DraftState implements OrderState {
  confirm(order: Order) { order.setState(new PendingState()); }
  pay() { throw new Error('Cannot pay a draft order'); }
  ship() { throw new Error('Cannot ship a draft order'); }
  cancel(order: Order) { order.setState(new CancelledState()); }
}

class PendingState implements OrderState {
  confirm() { throw new Error('Already confirmed'); }
  pay(order: Order) { order.setState(new PaidState()); }
  ship() { throw new Error('Cannot ship before payment'); }
  cancel(order: Order) { order.setState(new CancelledState()); }
}

状態遷移図

[Draft] ──confirm──→ [Pending] ──pay──→ [Paid] ──ship──→ [Shipped]
  │                     │                  │
  └──cancel──→ [Cancelled] ←──cancel──┘    │
                                           └──cancel──→ [Cancelled]

State パターンを使うと、この状態遷移図がコードに直接反映される。各状態クラスが「この状態から何ができるか」を定義する。

Strategy パターンとの違い

パターン 目的 切り替えのタイミング
State 内部状態に応じて振る舞いを変える 状態遷移時に自動的に切り替わる
Strategy アルゴリズムを外部から差し替える クライアントが明示的に選択する

State は「注文が支払い済みになったら、自動的に PaidState の振る舞いに切り替わる」。Strategy は「ソートアルゴリズムをクイックソートからマージソートに差し替える」。

TypeScript の Discriminated Union による代替

TypeScript では、State パターンをクラスではなく Discriminated Union で表現する方が簡潔な場合がある。

type OrderState =
  | { status: 'draft' }
  | { status: 'pending'; confirmedAt: Date }
  | { status: 'paid'; paidAt: Date }
  | { status: 'shipped'; shippedAt: Date }
  | { status: 'cancelled'; cancelledAt: Date };

function confirm(state: OrderState): OrderState {
  if (state.status !== 'draft') throw new Error(`Cannot confirm from ${state.status}`);
  return { status: 'pending', confirmedAt: new Date() };
}

Discriminated Union は不変 (Immutable) で、パターンマッチで網羅性チェックが効く。

ステートパターン vs switch 文

観点 switch 文 ステートパターン
拡張性 分岐を追加 新しいクラスを追加
開放閉鎖原則 違反 準拠
テスト 全分岐をテスト 状態ごとにテスト

State パターンを扱う関連書籍も多い。

関連用語