State パターン

State パターン（英: state pattern、ステート・パターン）とは、プログラミングで用いられる振る舞いに関する（英語版） デザインパターンの一種である。このパターンはオブジェクトの状態（state）を表現するために用いられる。ランタイムでそのタイプを部分的に変化させるオブジェクトを扱うクリーンな手段となる。 ドローソフトを例に取る。このプログラムは任意の時点においてさまざまなツールのうちの1つとして振る舞うマウスカーソルを持つ。複数のカーソルオブジェクトを切り替える代わりに、カーソルは現在使用されているツールを表す内部的な状態を保持する。（例えばマウスクリックの結果として）ツールに依存するメソッドが呼ばれると、メソッド呼び出しはカーソルの状態へと渡される。 各ツールは1つの状態に対応している。共有される抽象状態クラスはAbstractToolである。 class AbstractTool is function moveTo(point) is input: the location point the mouse moved to (this function must be implemented by subclasses) function mouseDown(point) is input: the location point the mouse is at (this function must be implemented by subclasses) function mouseUp(point) is input: the location point the mouse is at (this function must be implemented by subclasses) この定義により、各ツールはマウスカーソルの移動およびクリック／ドラッグの開始と終了を扱わなければならない。 その基底クラスを用い、単純なペンと範囲選択の各ツールを定義すると以下のようになる。 subclass PenTool of AbstractTool is last_mouse_position := invalid mouse_button := up function moveTo(point) is input: the location point the mouse moved to if mouse_button = down (draw a line from the last_mouse_position to point) last_mouse_position := point function mouseDown(point) is input: the location point the mouse is at mouse_button := down last_mouse_position := point function mouseUp(point) is input: the location point the mouse is at mouse_button := up subclass SelectionTool of AbstractTool is selection_start := invalid mouse_button := up function moveTo(point) is input: the location point the mouse moved to if mouse_button = down (select the rectangle between selection_start and point) function mouseDown(point) is input: the location point the mouse is at mouse_button := down selection_start := point function mouseUp(point) is input: the location point the mouse is at mouse_button := up この例では、コンテキストのためのクラスはCursorと呼ばれている。抽象状態クラス（この場合AbstractTool）で名付けられたメソッド群はコンテキストにおいても実装されている。コンテキストクラスではこれらのメソッドは、current_toolにより表される現在の状態の、対応するメソッドを呼び出す。 class Cursor is current_tool := new PenTool function moveTo(point) is input: the location point the mouse moved to current_tool.moveTo(point) function mouseDown(point) is input: the location point the mouse is at current_tool.mouseDown(point) function mouseUp(point) is input: the location point the mouse is at current_tool.mouseUp(point) function usePenTool() is current_tool := new PenTool function useSelectionTool() is current_tool := new SelectionTool Cursorオブジェクトが、適切なメソッド呼び出しをアクティブとなっているツールへと渡すことにより、異なる時点においてPenToolとSelectionToolのどちらとしてでも振舞えるのである。これがStateパターンの本質である。この場合では、PenCursorクラスとSelectCursorクラスを作ることで状態とオブジェクトを結合することも可能であり、単なる継承へと単純化できたであろうが、実践においては新しいツールが選択される毎に新しいオブジェクトへとコピーするにはコストがかかりすぎたりエレガントでなかったりするようなデータをCursorが保持していることもあるであろう。 英語版ウィキブックスに本記事に関連した解説書・教科書があります。 さまざまな言語でのStateパターンの実装 以下は状態（State）のインタフェースと2つの実装である。状態メソッドはコンテキストオブジェクトへの参照を持ち、その状態を変えることができる。 interface State { void writeName(StateContext stateContext, String name); } class StateA implements State { public void writeName(StateContext stateContext, String name) { System.out.println(name.toLowerCase()); // コンテキストをStateBに遷移する stateContext.setState(new StateB()); } } class StateB implements State { private int count = 0; public void writeName(StateContext stateContext, String name){ System.out.println(name.toUpperCase()); // StateBのwriteName()が2度呼び出された後にコンテキストをStateAに遷移する if (++count > 1) { stateContext.setState(new StateA()); } } } コンテキストクラスは、初期状態（この場合はStateA）でインスタンス生成したところの状態変数を持つ。そのメソッドでは、状態オブジェクトの対応するメソッドを使用する。 public class StateContext { private State myState; public StateContext() { setState(new StateA()); } // 通常は、Stateインタフェースを実装しているクラスによってのみ呼び出される public void setState(State newState) { this.myState = newState; } public void writeName(String name) { this.myState.writeName(this, name); } } 用法は以下。 public class TestClientState { public static void main(String[] args) { StateContext sc = new StateContext(); sc.writeName("Monday"); sc.writeName("Tuesday"); sc.writeName("Wednesday"); sc.writeName("Thursday"); sc.writeName("Saturday"); sc.writeName("Sunday"); } } TestClientStateからのmain()の出力は次のようになるはずである。 monday TUESDAY WEDNESDAY thursday SATURDAY SUNDAY