デザインパターンで状態を示す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パターンのように、状態をクラス化し、それを抽象的に操作してしまえば、
まだ見ぬ状態が出来たとしても、本体を変えることなくカスタマイズができる。
この、「本体を変えないでいい」というところが、すごかったりする。
ってことで、「へー(@_@!)」と感心された・・・
デザインパターンの説明って、なにが、どーすごいのか、わかんない例で書いているから、
いまいち、すごさが伝わらないのよね・・・