#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 問のクイズで、 このパターンを身体に染み込ませよう。