State パターンとは
振る舞いに関する(英語版) デザインパターンの一種である。このパターンはオブジェクトの状態(state)を表現するために用いられる。
Stateパターンでは、「状態」というものをクラスとして表現する。「状態」をクラスで表現していれば、クラスを切り替えることにより「状態の変化」を表すことができ、新しい状態を追加しなければならないときに何をプログラムすればよいかが明確になる。
example
時刻ごとに警備状態が変化する金庫警備システムを考える。 1時間ごとに擬似時刻を変化させ、その都度ランダムにアクションを実行させる。
仕様
- 金庫が1つある
- 金庫は警備センターと接続されている
- 金庫には非常ベルト通常通話のようの電話が接続されている
- 金庫には時計がついていて、現在時刻を監視している。
- 昼間は9:00-16:59, 夜間は17:00-23:59および0:00-8:59の範囲
- 金庫は昼間だけ利用可能
- 昼間、金庫を利用すると、警備センターに使用記録が残る
- 夜間、金庫を利用すると、警備センターに非常事態の通報が送信される
- 非常ベルはいつでも使用可能
- 非常ベルを使用すると、警備センターに非常事態の通報が送信される
- 通常通話用の電話はいつでも使用可能。(が、夜間は録音のみ)
- 昼間、電話を使用すると、警備センターが呼び出される
- 夜間、電話を使用すると、警備センターの留守録が呼び出される
State インターフェース
金庫の状態を表す。以下のアクションに対応して呼び出されるインターフェースを定義している。
- 時刻が設定されたとき
- 金庫が使用されたとき
- 非常ベルが押されたとき
- 通常通話を行うとき
<?php interface State { public function doClock(Context $context, int $hour); public function doUse(Context $context); public function doAlarm(Context $context); public function doPhone(Context $context); }
DayStateクラス
昼間の状態を表すクラス。状態を表すクラスは1つずつしかインスタンスを作らないことにする。 状態が変化するたびに新しいインスタンスを作るのはメモリと処理時間の無駄なのでSingletonパターンを使っている。
<?php class DayState implements State { private static $singleton; private function __construct() { } public static function getInstance(): State { if (self::$singleton === null) { self::$singleton = new self; } return self::$singleton; } public function doClock(Context $context, int $hour) { if ($hour < 9 || 17 <= $hour) { $context->changeState(NightState::getInstance()); } } public function doUse(Context $context) { $context->recordLog("use the strongbox(daytime)"); } public function doAlarm(Context $context) { $context->callSecurityCenter("security alert(daytime)"); } public function doPhone(Context $context) { $context->callSecurityCenter("call security center(daytime)"); } public function __toString(): string { return "[daytime]"; } }
NightStateクラス
夜間の状態を表すクラス。構成はDayStateクラスと同じ。
<?php class NightState implements State { private static $singleton; private function __construct() { } public static function getInstance(): State { if (self::$singleton === null) { self::$singleton = new self; } return self::$singleton; } public function doClock(Context $context, int $hour) { if (9 <= $hour && $hour < 17) { $context->changeState(DayState::getInstance()); } } public function doUse(Context $context) { $context->callSecurityCenter("emergency:use the strongbox at nighttime"); } public function doAlarm(Context $context) { $context->callSecurityCenter("security alert(nighttime)"); } public function doPhone(Context $context) { $context->recordLog("record of the night call"); } public function __toString(): string { return "[nighttime]"; } }
Contextクラス
状態の管理、警備センターへの呼び出しを行う。
<?php class Context { private $state; public function __construct(State $state = null) { if ($state instanceof State) { $this->state = $state; } else { $this->state = DayState::getInstance(); } } public function setClock(int $hour) { $clockstring = "now: "; if ($hour < 10) { $clockstring .= "0" . $hour . ":00"; } else { $clockstring .= $hour . ":00"; } echo $clockstring . PHP_EOL; $this->state->doClock($this, $hour); } public function changeState(State $state) { echo "state is change from " . $this->state . " to " . $state . PHP_EOL; $this->state = $state; } public function callSecurityCenter(string $msg) { echo "call! " . $msg . PHP_EOL; } public function recordLog(string $msg) { echo "record ... " . $msg . PHP_EOL; } public function doRandomAction() { switch (rand(0, 2)) { case 0: $this->state->doUse($this); break; case 1: $this->state->doAlarm($this); break; case 2: $this->state->doPhone($this); break; default: break; } } }
Main
テスト動作用
<?php $context = new Context(); for ($hour = 0; $hour < 24; $hour++) { $context->setClock($hour); $context->doRandomAction(); sleep(1); }
実行結果
$ php Main.php now: 00:00 state is change from [daytime] to [nighttime] call! security alert(nighttime) now: 01:00 call! emergency:use the strongbox at nighttime now: 02:00 call! security alert(nighttime) now: 03:00 call! security alert(nighttime) now: 04:00 call! emergency:use the strongbox at nighttime now: 05:00 call! emergency:use the strongbox at nighttime now: 06:00 call! emergency:use the strongbox at nighttime now: 07:00 call! emergency:use the strongbox at nighttime now: 08:00 record ... record of the night call now: 09:00 state is change from [nighttime] to [daytime] call! call security center(daytime) ... 中略 .... now: 18:00 call! emergency:use the strongbox at nighttime now: 19:00 call! security alert(nighttime) now: 20:00 record ... record of the night call now: 21:00 call! security alert(nighttime) now: 22:00 call! emergency:use the strongbox at nighttime now: 23:00 record ... record of the night call
ソースコード
役割
State役
状態を表す。状態毎に異なる振る舞いをするインターフェースを定義する。サンプルコードにおけるStateインターフェース。
ConcreteState役
具体的な個々の状態を表現する。State役で定められたインターフェースを具体的に実装する。 サンプルコードにおけるDayState, NightStateクラス。
Context役
現在の状態を表すConcreteState役を持つ。 サンプルコードにおけるContextクラス。
まとめ
分割統治
Stateパターンは、システムの状態をクラスとして表現することで、複雑なプログラムを分割する
状態に依存した処理
Stateインターフェースで宣言されているメソッドはすべて「状態に依存した処理」である。「状態に依存した処理」は、プログラム上で以下のように表現される。
- 抽象メソッドとして宣言し、インターフェースとする。
- 具象メソッドとして実装し、個々のクラスとする。
自己矛盾が起こらない
Stateパターンでは状態をクラスで表現する。現在の状態を表す変数はたった1つである。サンプルコードではstateプロパティがシステムの状態を決定している。
新しい状態の追加は容易
サンプルコードで言えば、Stateインターフェースを実装したXXXStateクラスの追加は容易。逆にStateインターフェースに新しいメソッドを追加することは、全てのXXXStateクラスへの変更が必要となり、困難となる。
関連パターン
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (399件) を見る