State パターン

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動: 案内検索
UMLによるStateパターン[1][2]
Lepus3英語版によるStateパターン[2][3]

State パターン: state pattern、ステート・パターン)とは、プログラミングで用いられる振る舞いに関する英語版 デザインパターンの一種である。このパターンはオブジェクトの状態(state)を表現するために用いられる。ランタイムでそのタイプを部分的に変化させるオブジェクトを扱うクリーンな手段となる[1]:395

擬似コードによる例[編集]

ドローソフトを例に取る。このプログラムは任意の時点においてさまざまなツールのうちの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が保持していることもあるであろう。

Javaによる例[編集]

以下は状態(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()); 
		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度呼び出された後に状態を変化させる
		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


脚注[編集]

関連項目[編集]

外部リンク[編集]