gof-state について
状態に応じてオブジェクトの振る舞いが変わる場面を扱う、GoF State パターンのスキルです。コンテキスト、状態インターフェース、具体状態を説明し、大きな状態分岐や不正な遷移への対処を示します。
主な機能
- 01振る舞いを状態クラスにカプセル化
- 02現在の状態へ操作を委譲
- 03状態図で遷移を表現
- 04State と Strategy の比較
- 05不正な状態遷移への対処
活用シーン
- 01状態 enum が大きな条件分岐を生む場合
- 02注文や接続に明確なライフサイクル状態がある場合
- 03状態と遷移の範囲が限定されている場合
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ yonatankarp/ software-design-skills --skill gof-stateインストール後、Claude Code で/gof-stateを入力して読み込まれたか確認します。
詳細
1 行でまとめると
オブジェクトの内部状態が変化したときに、その振る舞いを変えられるようにします。これにより、オブジェクトがクラスを変えたかのように見えます。各状態はそれぞれ独自のクラスにカプセル化され、コンテキストは現在保持している状態に振る舞いを委譲します。
このスキルを使う場面
- オブジェクトに複数の明確な状態があり、どの状態にあるかによって振る舞いが大きく変わります(ガムボールマシン、注文のライフサイクル、ネットワーク接続、パーサーなど)。
- 現在の実装に、状態を表す列挙型に対する大きな
switchまたはif-elseがあり、それぞれの分岐で大きく異なる処理を行っています。 - 状態の種類が有限で既知であり、状態間の遷移が限定され、ルールに従っています。
このスキルを使わない場面
- 状態が 2 つだけで、振る舞いの違いもごくわずかな場合は、単一のブール値フラグと小さな
ifのほうがシンプルです。 - 「状態」が振る舞いではなく、実際にはデータである場合は、ステートマシンではなく値オブジェクトです。
- 状態が相互排他的ではなく、複数のフラグが同時に true になる場合は、ステートマシンではなく機能フラグの組み合わせです。
主な内容
このパターンは、次の 3 つの要素で構成されます。
- コンテキスト — 振る舞いが変化するオブジェクト(ガムボールマシン)です。現在の
Stateオブジェクトへの参照を保持し、そのオブジェクトに処理を委譲する操作を公開します。 - State インターフェース — 状態によって変化する操作(
insertQuarter()、ejectQuarter()、turnCrank()、dispense())を宣言します。 - 具象状態 — State インターフェースを実装し、それぞれが固有の振る舞いを持つ特定の状態を表します。状態遷移は通常、状態自体によって進められます。現在の状態が入力を受け取り、コンテキストの次の状態を設定します。
各状態を(ステートレスな)シングルトンにする方法と、各状態がコンテキストごとの状態を保持する方法があります。まずは、よりシンプルな方法を選んでください。
State と Strategy の違い。 構造は同じです。切り替え可能な振る舞いオブジェクトに処理を委譲するコンテキストから成ります。異なるのは意図です。
- Strategy:クライアントが、構築時にどのアルゴリズムを使うかを選びます。
- State:操作の結果として、コンテキストが内部で自身の状態を変化させます。
判断のヒント
- 「状態に対する大きな switch」というコードのにおいに気づいたら、State パターンを検討する典型的な理由です。
- まず状態図を定義してください(どの状態が存在し、どの遷移が許可されるか)。状態クラスは、状態図と 1 対 1 に対応させます。
- 遷移は、コンテキストに集約する方法(よりシンプルですが、コンテキストがすべての状態を把握します)と、状態クラスに集約する方法(より分散されますが、各状態が次の状態を把握する必要があります)のいずれかを選びます。
アンチパターン
- 状態の増殖 — 振る舞いのわずかな違いごとに新しい状態クラスを作成します。その違いが本当に異なる状態を表すのか、それとも単なるパラメーターなのかを再検討してください。
- コンテキストのメソッドを通さずに、状態がコンテキストの状態を直接変更します。これにより、状態クラスがコンテキストの内部実装に依存します。
- 「状態」に実際の振る舞いの違いがないユースケースに State を適用します。パターンを使うのではなく、パターンにちなんで名前を付けているだけです。
- 不正な遷移への対応を忘れます。
Dispensed状態でinsertQuarter()が呼び出された場合は、何もせず黙って処理を終えるのではなく、明示的に応答する必要があります。
関連項目
gof-strategy— 構造は同じですが、意図(そして誰が選ぶか)が異なります。gof-command— 操作が状態遷移を引き起こす場合、その操作自体を Command にできます。gof-kotlin-idioms— Kotlin のシールドクラスと網羅的なwhenを使うと、状態ごとにサブクラスを作らずに、コンパイル時に検査されるステートマシンを実現できます。
参考文献
- Head First Design Patterns 第 2 版、第 10 章(「The State of Things: the State Pattern」)— No-Quarter/Has-Quarter/Sold/Sold-Out の状態を持つガムボールマシンを題材にした、標準的な解説です。
コメント
Markdown 対応