ウィリアムのいたずらの、まちあるき、たべあるき

ウィリアムのいたずらが、街歩き、食べ物、音楽等の個人的見解を主に書くブログです(たま~にコンピューター関係も)

Stateパターンのどこがすごいのか、感心されたので、書いてみる

2009-08-19 18:21:00 | Weblog

 デザインパターンで状態を示すStateパターンっていうのがある。
 これを説明して、「Stateパターン、すげー(@_@!)」
って感心されたんで、書いておいて見る。




■そもそも、Stateパターンとは?

状態を示す(抽象的な)スーパークラスStateをつくり、
(具体的な)各状態は、それを継承した、各状態クラスを作ります。

例:Stateクラスには、executeがある
public interface State {
	public State execute(State sts);
}


状態A:StateAクラスでは、実行executeで処理Aを行い、次に処理Bに行くため、実行Bを行う

public class StateA implements State{
	/*
	 * コンストラクタ
	 */
	StateA(State sts)
	{
		//	ここで初期値を設定する
		//	引数をStateB,StateC等、具体的にして、
		//	設定してもいい
	}
	
	public State execute(State sts)
	{
		//	処理Aの実行(ここでは出力のみ)
		System.out.println("処理A実行したとする");
		
		//	かえる(次の処理Bセット)
		return new StateB(this);
	}

}



状態B:StateBクラスでは、実行executeで処理Bを行い、ここで処理を終了する。
public class StateB  implements State{
	/*
	 * コンストラクタ
	 */
	StateB(State sts)
	{
		//	ここで初期値を設定する
		//	引数をStateA,StateC等、具体的にして、
		//	設定してもいい
	}
	
	public State execute(State sts)
	{
		//	処理Bの実行(ここでは出力のみ)
		System.out.println("処理B実行したとする");
		
		//	かえる(null=終了)
		return null;
	}
}


こうすると、以下のように、ステータスに応じた処理をします。
<<呼び出しmain部分>>
public class Main {
	public static void main(String args[])
	{
		//	開始点は状態A
		State sts = new StateA(null);
		//	次の状態がnullになるまで
		while(sts != null)
		{
			//	実行する
			sts = sts.execute(sts);
		}
	}
}


この結果は、当然
処理A実行したとする
処理B実行したとする

となる。




■ここで、おさえておきたいこと

これをもし、ふつーに、状態遷移の変数で書くことも出来る。
こんなかんじ?
public class Main {
	public static void main(String args[])
	{
		int stsno = 1;
		State sts = null;
		//	次の状態がnullになるまで
		while(stsno != 0)
		{
			//	実行する
			switch(stsno)
			{
			case	1:
				sts = new StateA(null).execute(sts);
				stsno	=	2;
				break;
			case	2:
				sts = new StateB(sts).execute(sts);
				stsno	=	0;
				break;
			}
		}
		
	}
}

となる。
このように、状態遷移の番号を使って、switch文で分岐するっていうことは、
イベントドリブンの場合の普通の書き方だ(たとえばBREWとか)




■Stateパターンのどこがすごいのか

で、ここで、

ステータスCを追加し、
Aが終了したら、Cを実行した後、Bを実行することにする。

そうすると、Cを追加したんだから、Cを作らないといけないのはあたりまえ、
public class StateC implements State{
	/*
	 * コンストラクタ
	 */
	StateC(State sts)
	{
		//	ここで初期値を設定する
		//	引数をStateB,StateC等、具体的にして、
		//	設定してもいい
	}
	
	public State execute(State sts)
	{
		//	処理Aの実行(ここでは出力のみ)
		System.out.println("処理C実行したとする");
		
		//	かえる
		return new StateB(this);
	}
}


呼び出すAもBからCへ、変えないといけないのは、こりゃ、しょうがない

public class StateA implements State{
	/*
	 * コンストラクタ
	 */
	StateA(State sts)
	{
		//	ここで初期値を設定する
		//	引数をStateB,StateC等、具体的にして、
		//	設定してもいい
	}
	
	public State execute(State sts)
	{
		//	処理Aの実行(ここでは出力のみ)
		System.out.println("処理A実行したとする");
		
		//	かえる=ここ、BからCに変わった
		return new StateC(this);
	}

}


だけど、逆に言うと、ここしか、かわらないのだ!
状態Bも、呼び出し元Mainも、何も変えなくても動く
処理A実行したとする
処理C実行したとする
処理B実行したとする


ところが、状態遷移の変数を使って行う場合だと、Mainを変えないといけない。




■具体的なケースでかんがえてみると・・・

ここで、今の話を具体的に考えてみる。
Mainのクラスをパッケージソフト、StateA,B,Cをカスタマイズ部分だとする。


状態遷移の変数を使う方法の場合は、カスタマイズするたびに状態遷移が増えるから、
パッケージソフト(Main部分)を修正しないといけない。
ということは、状態遷移すべてまとまらないと、コーディングできないのだ!
そのうえ、Main部分を修正してくれるまで、StateCのテストはできない。
・・・っていうか、カスタマイズのためにパッケージソフト本体を直すのは最悪だ(>_<!)


一方、Stateパターンのほうは、Mainは、一切、手が入ってない。
StateAとStateCのカスタマイズ部分だけだ。
状態遷移図を書いた場合、変更箇所前後だけを治せばいいので、わかりやすい。
自分の呼び出し前後側だけ、対応してくれれば、全体は、どーでもよく、切り替え可能だ。


このStateパターンのように、状態をクラス化し、それを抽象的に操作してしまえば、
まだ見ぬ状態が出来たとしても、本体を変えることなくカスタマイズができる。
この、「本体を変えないでいい」というところが、すごかったりする。




ってことで、「へー(@_@!)」と感心された・・・

デザインパターンの説明って、なにが、どーすごいのか、わかんない例で書いているから、
いまいち、すごさが伝わらないのよね・・・

この記事についてブログを書く
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする
« ”ゴールドマン自己「0.03... | トップ | 薄型PS3、HDD120GB搭載で2998... »
最新の画像もっと見る

Weblog」カテゴリの最新記事