状態をハンドリングするのがStateパターンです。実際に、Javaでのプログラムロジックを見ながら説明していきます。状態をハンドリングする一般的なロジックは以下のようになります。
public boolean handleEvent(Event evt) {
int& state;
switch (evt.id) {
case Event.COIN_INSERT:
if (vendmachine.CoinInsert(state))
state = COIN_INSERT;
break;
case Event.COIN_COMPLETE:
if (vendmachine.CoinComp(state))
state = COIN_ COMPLETE;
break;
}
}
ここでの例は、エンジンボックスというプログラムで、ギアをアップ、ダウンする機能を持っています。状態としては、待ちと動作中があります。
状態モデルは図2のようになります。非常にシンプルな例です。
この状態である、Idle、Runningをそれぞれ、EngineStateのサブクラスにします。そのサブクラスに、それぞれの状態で行う振る舞いをメソッドとして記述します。それでは、実際にJavaのコードにするとどうなるでしょう。
abstract class EngineState{
void Up(EngineBox eb){}
void Down(EngineBox eb){}
void Error(EngineBox eb){}
void ShowCurrentState(){}
}
class EngineRunning extends EngineState{
PrintPanel pt = new PrintPanel();
void Up(EngineBox eb){
pt.Print("No More Up");
eb.ChangeState(new EngineRunning());
}
void Down(EngineBox eb){
pt.Print("Down to idle from running");
eb.ChangeState(new EngineIdle());
}
void ShowCurrentState(){
pt.Print("State:running");
}
}
class EngineIdle extends EngineState{
PrintPanel pt = new PrintPanel();
void Up(EngineBox eb){
pt.Print("Up to running from idle");
eb.ChangeState(new EngineRunning());
}
void Down(EngineBox eb){
pt.Print("No More Down");
eb.ChangeState(new EngineIdle());
}
void ShowCurrentState(){
pt.Print("State:idle");
}
}
class EngineBox extends Object{
EngineState state;
EngineBox(){
state = new EngineIdle();
}
void Up(){
state.Up(this);
}
void Down(){
state.Down(this);
}
void ChangeState(EngineState newState){
state = newState;
state.ShowCurrentState();
}
}
コードの説明をしていきますと、EngineBoxクラスの初期状態は図2の状態モデルよりIdleになります。これは、state = new EngineIdle();で行えます。
また、Up()を見てみますと、EngineBoxのオブジェクトであるthis (自分自身)をパラメータとして渡しています。これは、Up()の中から、EngineBoxのメソッドChangeState()を呼び出しているためです。
この時、状態を変えるために、new EngineRunning()として、IdleからRunningに状態を変更しているのです。EngineIdle、EngineRunningには、Up()、Down()にぞれぞれの振る舞いをコーディングしてあります。
OOPはパズルを解くようにトリッキーなので、皆さんもこのプログラムを動かしながら、ロジックを考えてみましょう。メイン部分の呼び出しは、単純で、次のようになります。
EngineBox ebox;
ebox = new EngineBox();
ebox.Up();
イベントによって、ebox.Down()またはebox.Up()を呼び出します。
ここで、仕様を変更して、稼働中の状態にもうひとつ追加して、ローとハイにしてみましょう。Stateパターンでは、柔軟性があるので、サブクラスに少しの変更を入れるだけですみます。メインのEngineBoxクラスに変更はありません。
クラス図は、図4のようになります。
Stateパターンを使わないコードに新しい状態を追加すると、CASE文が多くのソースコードに散らばっているので、修正作業に手間がかかり、バグを作りやすくなります。
これに比べて、Stateパターンでは、追加したい状態のサブクラスを作り、その状態にあった振る舞いにメソッドを変更すれば済みます。EngineBoxクラスのコードに修正を加える必要はないのです。
<<修正したJavaのコード>>
abstract class EngineState{
void Up(EngineBox eb){}
void Down(EngineBox eb){}
void ShowCurrentState(){}
}
class EngineRunningLow extends EngineState{
void Up(EngineBox eb){
System.out.println("Up to running high from low");
eb.ChangeState(new EngineRunningHigh());
}
void Down(EngineBox eb){
System.out.println("Down to idle from running low");
eb.ChangeState(new EngineIdle());
}
void ShowCurrentState(){
System.out.println("State:running low");
}
}
class EngineRunningHigh extends EngineState{
void Up(EngineBox eb){
System.out.println("No More Up");
eb.ChangeState(new EngineRunningHigh());
}
void Down(EngineBox eb){
System.out.println("Down to running low from high");
eb.ChangeState(new EngineRunningLow());
}
void ShowCurrentState(){
System.out.println("State:running high");
}
}
class EngineIdle extends EngineState{
void Up(EngineBox eb){
System.out.println("Up to running low from idle");
eb.ChangeState(new EngineRunningLow());
}
void Down(EngineBox eb){
System.out.println("No More Down");
eb.ChangeState(new EngineIdle());
}
void ShowCurrentState(){
System.out.println("State:idle");
}
}
class EngineBox extends Object{
EngineState state;
EngineBox(){
state = new EngineIdle();
}
void Up(){
state.Up(this);
}
void Down(){
state.Down(this);
}
void ChangeState(EngineState newState){
state = newState;
state.ShowCurrentState();
}
}
このようにStateパターンは、状態によって同じメソッド名で内容を変えたい場合に、有効に機能します。デメリットとしては、状態が多くなるとその分だけ、サブクラスを作成しなければいかんくなることです。