【デザインパターン】ステートパターン 前編
どうも、研究で現在高速化を行っているのですが、やはり大変ですね。
ただ、カメラの前でパラメータ調整しながらジェスチャをし続ける日々からは抜け出せて、がっつりプログラミングが出来て凄く楽しいです。
さて、今日は【State】パターンについてです。
ソースコードが相当に長いので、今回は二回に分けて書いていきます!
一覧
この記事は以下の様な流れで記述される。
- Stateパターンって何ですか?
- Stateパターンのメリット
- 具体的な例
Stateパターンって何ですか?
Stateパターンとは内部状態が変化した際にオブジェクトがその振る舞いを変更できるパターン。
自動販売機の様に明確なオートマトンが定義されている場合は有効に扱える。
Wikipediaさんも参照。
State パターン(英: state pattern、ステート・パターン)とは、プログラミングで用いられる振る舞いに関する(英語版) デザインパターンの一種である。このパターンはオブジェクトの状態(state)を表現するために用いられる。ランタイムでそのタイプを部分的に変化させるオブジェクトを扱うクリーンな手段となる
Stateパターンのメリット
- Stateパターンは状態に対して動作毎に明確な振る舞いを定義することが出来る
- 状態毎に動作の定義をまとめること出来るので可読性が上がる
具体的な例
さて、いつも通り参考書から例を出していきましょう。
とある製造メーカからガムボールマシン(10円を入れて回すと出てくるアレ)を実装して貰いたいという依頼がありました。
(英語ですが、これがその状態遷移表です)
機械は25セントを投入し、クランクを回し、ガムボールが排出されます。
その時にガムボールの数が0個になれば販売を終了します。
さて、ではまずはこの状態遷移表に則って、デザインパターンを使わずに作っていきましょう。
まずは25セントが投入された時の動作を状態毎に定義したコードを以下に掲載していきましょう。
以下がそのコードになります。
// こんな感じで状態を定義 final static int SOLD_OUT = 0; // 売り切れ final static int NO_QUARTER = 1; // 25セント未受領 final static int HAS_QUARTER = 2; // 25セント受領 final static int SOLD = 3; // 販売状態 // 25セントを入れる動作 public void insertQuareter() { if(state == HAS_QUARTER) // 既に25セントを入れている状態 { System.out.println("もう一度25セントを投入することはできません"); } else if(state == SOLD_OUT) // 売り切れ状態 { System.out.println("25セントを投入することはできません。このマシンは売り切れです。"); } else if(state == SOLD) // ガムボール排出中 { System.out.println("お待ちください。既にガムボールを販売中です。"); } else if(state == NO_QUARTER) // 25セント入れていない状態 { state = HAS_QUARTER; System.out.println("25セントを投入しました"); } }
さて、こんな感じでずらずらと書いていくとブログが以上に長くなるので、省略しながら全体像を書いていきます。
public class GumballMachine { final static int SOLD_OUT = 0; // 売り切れ final static int NO_QUARTER = 1; // 25セント未受領 final static int HAS_QUARTER = 2; // 25セント受領 final static int SOLD = 3; // 販売状態 int state = SOLD_OUT; // 初期状態を売り切れに設定 int count = 0; // ボールの数 public GumballMachine(int count) { this.count = count; // ガムボールがあれば25セント未受領状態へ以降 if(count > 0) { state = NO_QUARTER; } } // 25セントを入れる動作 public void insertQuareter() { if(state == HAS_QUARTER) // 既に25セントを入れている状態 { System.out.println("もう一度25セントを投入することはできません"); } else if(state == SOLD_OUT) // 売り切れ状態 { System.out.println("25セントを投入することはできません。このマシンは売り切れです。"); } else if(state == SOLD) // ガムボール排出中 { System.out.println("お待ちください。既にガムボールを販売中です。"); } else if(state == NO_QUARTER) // 25セント入れていない状態 { state = HAS_QUARTER; System.out.println("25セントを投入しました"); } } // 25セントの払い戻し動作 public void ejectQuareter() { if(state == HAS_QUARTER) // 既に25セントを入れている状態 { System.out.println("25セントを返却しました"); state = NO_QUARTER; } // 省略 } // 25セントの払い戻し動作 public void ejectQuareter() { if(state == HAS_QUARTER) // 既に25セントを入れている状態 { System.out.println("25セントを返却しました"); state = NO_QUARTER; } // 省略 } // クランクを回す public void turnCrank() { // 省略 if(state == HAS_QUARTER) // 既に25セントを入れている状態 { System.out.println("クランクを回しました…"); state = SOLD; dispense(); // そのままガムの販売に移行 } // 省略 } // ガムボール販売 public void dispense() { if(state == SOLD) // ガムボール排出中 { System.out.println("ガムボールがスロットから転がり出てきます"); count--; if(count == 0) { Sysmte.out.println("ガムボールがなくなりました!"); state = SOLD_OUT; } else { state = NO_QUARTER; } } // 省略 } }
さて、このような感じになっています。
既にこのソースコードを見て嫌な予感を感じている方も多いでしょう
1つのクラスに固まっている大量の条件分岐。
さて、これに仕様が追加された場合一体どんな恐ろしい事が発生するのでしょうか?
そこで出てくるのがStateパターンです。
次回は新たに追加される要件に対して、Stateパターンでどのようにソースコードが変化するかを見てみましょう!