はじめに
私たちのチームでは2年ほど前からAndroidやiOSで設計をするときにステートマシンを使っているのですが、界隈の方々と話していていてあんまり聞いたことがないので、ちょっと使い方どころなどを紹介してみたいと思います。
やりたいことをイメージする
題材として位置情報を取得して画面を更新するいうシーンを考えてみます。
位置情報を取得するクラス(LocationClient, GoogleApiClient)を見てみると
基本的には、
- Activity(Fragment)のonStartでサービスに接続する
- 接続ができたら位置情報をリクエストする
- 取得した位置情報をもとに画面を更新する
という流れになると思います。
ここまでは普通ですね?
まずは使ってみる
下調べができたので、ステートマシンを作ります。SMCという自動でコード生成してくれる便利なツール( http://smc.sourceforge.net/
)があるので、これを使いましょう。
%package com.example.smc.location
%fsmclass LocationControllerStateMachine
%class LocationControllerAction
%start LocationControllerStateMap::STOPPED
%map LocationControllerStateMap
%%
STOPPED
{
start CONNECTING {}
}
CONNECTING
Entry { connect(); }
{
notifyConnected UPDATING {}
}
UPDATING
Entry { updateLocation(); }
{
locationChanged READY { onLocationChanged(); }
}
READY
Entry { onReady(); }
{
}
Default
{
Default nil {}
}
%%
隠れた仕様を作り込む
あとは、エラーの時とか、途中で画面遷移しちゃった時とか考慮すれば大丈夫なはずです。
画面がバックグラウンドに回った時のイベント(stop), エラー(notifyError)を追加してみます。
%package com.example.smc.location
%fsmclass LocationControllerStateMachine
%class LocationControllerAction
%start LocationControllerStateMap::STOPPED
%map LocationControllerStateMap
%%
STOPPED
Entry { disconnect(); }
{
start CONNECTING {}
stop nil {}
notifyError nil {}
}
CONNECTING
Entry { connect(); }
{
notifyConnected UPDATING {}
}
UPDATING
Entry { updateLocation(); }
{
locationChanged READY { onLocationChanged(); }
}
READY
{
updateLocation UPDATING {}
}
NO_CONNECTION
{
notifyError nil {}
}
Default
{
stop STOPPED {} /* onStopが呼ばれたことを示すイベント */
notifyError NO_CONNECTION {}
Default nil {}
}
%%
機能追加も簡単
位置情報は取得するのに時間がかかることもあります。タイマーで時間を計りタイムアウトするようにしてみましょう。
%package com.example.smc.location
%fsmclass LocationControllerStateMachine
%class LocationControllerAction
%start LocationControllerStateMap::STOPPED
%map LocationControllerStateMap
%%
STOPPED
Entry { disconnect(); }
{
start CONNECTING {}
stop nil {}
notifyError nil {}
}
CONNECTING
Entry { connect(); }
{
notifyConnected UPDATING {}
}
UPDATING
Entry { setTimer(); updateLocation(); } /* タイマー開始 */
Exit { clearTimer(); } /* タイマー止める */
{
locationChanged READY { onLocationChanged(); }
notifyTimeout READY { onTimeout(); } /* ここにタイマー追加 */
}
READY
{
updateLocation UPDATING {}
}
NO_CONNECTION
{
notifyError nil {}
}
Default
{
stop STOPPED {}
notifyError NO_CONNECTION {}
Default nil {}
}
%%
おわりに
こんな感じで、さくさくと設計を進めることができます。
また、Androidを想定して作ってみましたが、そんなにOS依存なところはありませんので、iOSなど他のプラットホームにも簡単に移植できます。
自動で図を描いてくれるツールなどもありますので、他の人から引き継いだステートマシンも容易に理解できます。(たくさんのフラグで制御されている実装だとプログラムの流れを読み解いていかないといけない。)
こんな感じで便利なので、設計にはステートマシンがおすすめです。