デザインパターン[State]

この前入れたVineLinuxが良い感じです。
つか、このGUI気に入った。 インストールが簡単ならwindowsから乗り換えてもいいかなって本気で思うわ。

・・・とそれはおいておいて、
特に書くこともないので、
自分への復習も兼ねてこれからGoF他もろもろのデザインパターンを紹介して行こうと思う。
他のサイト調べればそのくらいの情報は載ってるよーって事しか書かない(書けない?)ので、役に立つかどうかは不明です。

では、初っ端は。Stateパターン。
一番最初に紹介すべきパターンではないかもしれませんが・・・

まぁいいや。

Stateパターンの目的:
 複数の状態が常に変動するようなものを簡単にする。

Stateパターンの概要:
 一つの状態を一つのオブジェクトで表現。
 全ての状態が取りうる動きをメソッドとして定義し、
 その状態が取りうる状態の処理のみを記述する。
 その結果の状態遷移も行う。

Stateパターンで登場するクラス・インターフェイス
・Stateインターフェイス (State)
・Stateインターフェイスを実装した状態クラス (StateImpl)
・全てのState実装クラスを管理するクラス (Context)

各クラスの説明:
State
 システムが取りうる動きをメソッドとして持つ。
 全てのメソッドが引数としてContextを受け取る。
StateImpl
 システムの状態1つ1つがクラスとなる。
 その状態が取りうる処理と状態遷移を記述。
 引数のContextを操作することで↑を行う。
Context
 全てのStateImplをインスタンスとして持つ。
 また、現在の状態のStateも合わせて持つ。
 全ての状態が取りうる全ての処理、
 状態を変更する処理をここに記述する。

実際に使用するのは、Contextのみ。
状態によってメソッドの動きが変わるため、
使用者は単純に呼び出せばよい。

下に簡単な例を示す。


// ビデオデッキの例 Javaです。

// 状態インターフェイス
interface State {
void play(Context c);
void stop(Context c);
void pause(Context c);
}

// 再生中状態クラス
class PlayState implements State {
public void play(Context c) {
// 再生中なので何もしない
}
public void stop(Context c) {
c.doStop();
c.setStopState();
}
public void pause(Context c) {
c.doPause();
c.setPauseState();
}
}

// 停止中状態クラス
class StopState implements State {
public void play(Context c) {
c.doPlay();
c.setPlayState();
}
public void stop(Context c) {
// 停止中なので何もしない
}
public void pause(Context c) {
// 停止中なので何もしない
}
}

// 一時停止中状態クラス
class PauseState implements State {
public void play(Context c) {
c.doPlay();
c.setPlayState();
}
public void stop(Context c) {
c.doStop();
c.setStopState();
}
public void pause(Context c) {
c.doPlay();
c.setPlayState();
}
}

// コンテキストクラス 利用者はこいつをいじる。
public class Context {

// 状態クラスインスタンス。 static finalで持つ。
private static final State PLAY_STATE = new PlayState();
private static final State STOP_STATE = new StopState();
private static final State PAUSE_STATE = new PauseState();

// 現在の状態 初めは停止状態にしておく。
private State currentState = STOP_STATE;

// 利用者が使うメソッドはこの3つ。
public void play() {
currentState.play();
}

public void stop() {
currentState.stop();
}

public void pause() {
currentState.pause();
}

// 実際の処理
void doPlay() {
// 再生処理
}

void doStop() {
// 停止処理
}

void doPause() {
// 一時停止処理
}

// 状態遷移メソッド
void setPlayState() {
currentState = PLAY_STATE;
}
void setStopState() {
currentState = STOP_STATE;
}
void setPauseState() {
currentState = PAUSE_STATE;
}
}

ざっと書いてみた。一番簡単なStateパターンの実装になるだろう。
実のところ、Contextには、「状態を管理する」と「実処理を行う」の2つの責任があるので、単一責任の原則(SRP)に違反している。
実処理を行うクラスを別にして、そいつを呼び出すようにするのがベターだといえる。 ただ、さほど難しい処理でなければこのままでも構わない気もしなくもない。 状態管理といっても、実際に状態を変更しているのはStateImpl側なので。
そのあたりは、システムの複雑度の折り合いを見て判断することになるだろう。


実際書いてみて、これは簡単な割りに複雑な状態を簡単に管理できると実感。 実のところ実ソースを書いてみたのはこれが初めて。
これは使いどころさえあれば非常に便利だ。

間違いがあれば指摘していただけるとこれ幸いです。

参考図書: アジャイルソフトウェア開発の奥義