State パターン(GoF)を適用したくなるケースとしては、同じメソッドでも 状態 によって処理の内容が変わる場合、かつ、if
文等による分岐処理を書くと複雑になりコードの見通しが悪くなってしまう場合。または 状態 に関連するコードを一箇所に集めて見通しを良くしたい場合。
例:通信状態の場合はローディングを表示したい、ボタン押下を無効にしたい等
今回は次のような 状態 と 遷移 の簡単なステートマシンを実現したいとします。
[ Idle ]
↓ doApiCall()
[ Processing ]
これを Java の Enum
で表現すると次のようになります。今回は Enum
の定義を Activity のコードの中に内包します。
コード量が多くなる場合は
Enum
用のファイルを別に作ってもよいかと思います。また状態をEnum
でなくクラスで表現する方法もあります。
public class SampleActivity extends AppCompatActivity {
// ...
// 普通に Activity のロジックを書く。今回は略
// ...
private State state = State.Idle; // 初期状態
// 状態を変更するメソッド
private void changeState(State nextState) {
state.exit(this);
state = nextState;
state.enter(this);
}
// ステートマシン。状態別に処理させたい内容を定義
private enum State {
Idle {
@Override
public void doApiCall(SampleActivity parent) {
parent.changeState(Processing); // 状態を Processing へ変更
// 何か通信処理 ..
}
},
Processing {
// 今回は略してますが enter() でローディング表示など書く
},
;
public void enter(SampleActivity parent) {} // 状態に入った際の処理を何か定義したい場合はオーバーライドする
public void doApiCall(SampleActivity parent) {} // 中身が空なのでオーバーライドしなければ呼びだされても何も処理されない
public void exit(SampleActivity parent) {} // 状態から出た際の処理を何か定義したい場合はオーバーライドする
}
}
Enum
の中で直にSampleActivity
を参照していますがInterface
を定義して参照しても良いかと思います。
あとは Actvity のどこかで state.doApiCall(this)
とすれば通信処理が始まり、状態は Processing
に遷移します。Processing
状態では doApiCall()
を受け付けません(オーバーライドしていないので)。
今回は省略していますが、通信処理が終わったらまた状態を Idle
に戻せばよいかと思います。
State パターンでは状態遷移をどこから指示するか問題があり、上記の例では状態遷移メソッド changeState()
を Enum
の定義の中から呼び出していますが、状況に応じて Activity 側から changeState(State.Processing)
のように呼び出して遷移させても構いません。
ほか
- 状態数が少ない場合やそもそも複雑でない場合は使わない方がよいかもしれない。
-
if
文で分岐している処理やフラグで分岐する処理は何でもこれで表現したくなる気分になるが、それを「状態」として昇格するか吟味すること。- フラグはフラグで分かりやすいケースは多い。
- Rx のストリームの中で分岐処理を書きたい場合に有用かも(未検証)。