DPDesign Pattern Quest
#20 / 23
B振る舞いDIFF

ステート

State

信号機。青なら『進め』、赤なら『止まれ』、黄色なら『注意して止まれ』── 状態が変われば、振る舞いも丸ごと変わる。

Intent · 目的

オブジェクトの『状態』をクラスとして取り出し、状態に応じた振る舞いをそのクラスに委譲する。利用側は `current.action()` と呼ぶだけで、状態ごとの分岐が消える。

Motivation · 動機

ドキュメントに『下書き / レビュー中 / 公開済 / アーカイブ』の4状態があるとする。`publish()` メソッドの中身は、下書きならレビュー送信、レビュー中なら公開、公開済なら何もしない、アーカイブなら例外…と分岐だらけになる。`save()` も `delete()` も同じく。状態判定の if が、メソッドという名のメソッドの全てに散らばる地獄。State パターンは『状態クラス』に振る舞いをまとめ、現在状態が `Draft` なら `Draft.publish()` を呼ぶだけ ── と差し替える。コードがあちこちで書き換わるのではなく、現在状態オブジェクトが入れ替わる、と発想を逆転させる。

適用場面

  • 1状態によって振る舞いが大きく変わるオブジェクトがある(注文、配送、ドキュメント、ゲームのキャラ)。
  • 2状態遷移のルールが複雑で、if/switch が散らばっている。
  • 3状態ごとに『許される操作』が異なる。

概念図

サンプルコード

interface DocState { publish(doc: Document): void; }

class Document {
  state: DocState = new Draft();
  publish(){ this.state.publish(this); }
}

class Draft implements DocState {
  publish(doc: Document){ console.log("→ review"); doc.state = new InReview(); }
}
class InReview implements DocState {
  publish(doc: Document){ console.log("→ published"); doc.state = new Published(); }
}
class Published implements DocState {
  publish(){ console.log("already published"); }
}

const d = new Document();
d.publish(); d.publish(); d.publish();
Pros · メリット
  • 状態ごとの振る舞いがクラスに分離される。`if state == ...` の地獄から完全卒業。
  • 状態遷移ルールが明示的になり、新メンバーも読み解きやすい。
  • 新状態の追加が局所的。クラス1個追加してハンドラを実装するだけ。
Cons · デメリット
  • 状態が3つや4つしかない単純なケースには過剰。素直な enum + 分岐で十分。
  • ファイル数・クラス数が増える。プロジェクトが小さいうちは煩く感じる。
  • 状態遷移を Context 側で管理するか、State 側で管理するかの判断が悩ましい(一覧性 vs 局所性)。

関連パターン

⚔ ready to test

理解度を確かめる時間だ

3 問のクイズで、 このパターンを身体に染み込ませよう。

挑戦する →