State パターン (状態を表すオブジェクトを用意し, 内包するその状態オブジェクトを切り替えることにより, 処理内容 (振る舞い) を変えられるようにする)

[State] = [状態] を意味する.

このパターンでは, ある物についての各状態をそれ対応した各クラスで表現する. (つまり, 状態一つにつき, クラス1つで表現する)

通常, 条件 (状態) に一致するかどうかの処理は, if 文を使用し, コーディングするが, その条件分岐が多い場合, 1つの条件で処理するコード量が多い場合, また同じ条件分岐が複数の個所に点在する場合など, メンテナンスしづらいものとなってしまう. しかし, このパターンを適用すると, その状態を個々のクラスで表現するため, 単純明快となる.

役割

  1. State (状態):
状態を表すクラスである. 状態毎に振る舞いがことなるメソッドのインタフェースを定義する.
  1. ConcreteState[A|B] (具体的な状態):
[State] のインタフェースを実装し, 具体的な状態を [1 クラス] = [1 状態] で定義する. 1つ状態を表すのに複数のオブジェクトは必要ないため, [Singleton] パターンを適用する.
  1. Context (状況判断):
現在の状態 ( [ConcreteStataA] か [ConcreteStateB] ) を保持する. 利用者へのインタフェースを定義する. 状態を変更するメソッドを定義する. (状態の変更は, [ConcreteState] が次々の状態として相応しいものを判断し, この状態変更メソッドを呼び出すことによって行う.)
  1. Client (利用者):
[State] パターンを適用したクラスを用い処理を行う.

クラス図

State パターンのクラス図

_images/designpattern-state012.gif

シーケンス図

State パターンのシーケンス図

_images/designpattern-state022.gif

ソースコード

  1. State.java
1
2
3
4
public interface State{
    public abstract void stateMethod1(Context context, int condition);
    public abstract void stateMethod2(Context context);
}

2-1. ConcreteStateA.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ConcreteStateA implements State{
    private static final String stateName = "ConcreteStateA";

    /* [Singleton] パターンを適用 */
    private static State concreteStateA = new ConcreteStateA();
    private ConcreteStateA(){}
    public static State getInstance(){
	return concreteStateA;
    }

    /* [Context] が参照しているアクティブな [State] オブジェクト変更用メソッド */
    public void stateMethod1(Context context, int condition){
	if(condition == 1){
	    context.setState(ConcreteStateB.getInstance());
	    System.out.println("状態変更: A -> B");
	}
    }
    
    public void stateMethod2(Context context){
	context.contextMethod3("状態: " + stateName);
    }
}

2-2. ConcreteStateB.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ConcreteStateB implements State{
    private static final String stateName = "ConcreteStateB";

    /* [Singleton] パターンを適用 */
    private static State concreteStateB = new ConcreteStateB();
    private ConcreteStateB(){}
    
    public static State getInstance(){
	return concreteStateB;
    }

    /* [Context] が参照しているアクティブな [State] オブジェクト変更用メソッド */
    public void stateMethod1(Context context, int condition){
	if(condition == 0){
	    context.setState(ConcreteStateA.getInstance());
	    System.out.println("状態変更: B -> A");
	}
    }

    public void stateMethod2(Context context){
	context.contextMethod3("状態: " + stateName);
    }
}
  1. Context.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Context{
    private State state = null;

    public Context(){
	state = ConcreteStateA.getInstance();
    }

    public void setState(State state){
	this.state = state;
    }

    public void contextMethod1(int conditon){
	state.stateMethod1(this, conditon);
    }

    public void contextMethod2(){
	state.stateMethod2(this);
    }

    public void contextMethod3(String msg){
	System.out.println(msg);
    }
}
  1. Client.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.Random;

public class Client{
    public static void main(String[] args){
	Random rand = new Random();
	Context context = new Context();

	int temp = 0;
	int condition = 0;
	
	for (int i = 0; i < 10; i++){
	    System.out.println("--------------------");
	    temp = rand.nextInt(10);
	    System.out.println(i + " 回目: " + temp);
	    condition = temp % 2;
	    System.out.println("        : " + condition);
	    context.contextMethod1(condition);
	    context.contextMethod2();
	}
    }
}

上記のプログラムの実行結果:

[wtopia State]$ java Client
--------------------
0 回目: 1
        : 1
状態変更: A -> B
状態: ConcreteStateB
--------------------
1 回目: 4
        : 0
状態変更: B -> A
状態: ConcreteStateA
--------------------
2 回目: 7
        : 1
状態変更: A -> B
状態: ConcreteStateB
--------------------
3 回目: 0
        : 0
状態変更: B -> A
状態: ConcreteStateA
--------------------
4 回目: 1
        : 1
状態変更: A -> B
状態: ConcreteStateB
--------------------
5 回目: 9
        : 1
状態: ConcreteStateB
--------------------
6 回目: 3
        : 1
状態: ConcreteStateB
--------------------
7 回目: 9
        : 1
状態: ConcreteStateB
--------------------
8 回目: 6
        : 0
状態変更: B -> A
状態: ConcreteStateA
--------------------
9 回目: 0
        : 0
状態: ConcreteStateA

上記のプログラムの状態遷移図は下記のようになる.

_images/state011.gif