目次
ステート図を書く時っていつ?
みなさんはステート図(状態遷移図)を書く機会はありますか?
ステート図を書く時は、状態を持つ(時間的な待ちが必要となる)物を作る場合です。
ですが、OSによってスレッドによる並列実行が行える場合は、ステート図を書くまでもなく解決できることもあるため、ステート図を使って設計しなくても何とかなる場面が間接的に増えてきたような気がします。
さらにC#などの最近の言語では、async / await的な機構が組み込まれてきたり、Reactive Extentionsのようなライブラリが出てきたこともあり、その傾向は強まっているようにも思います。
しかし本質的に状態を持つ処理というものは存在する以上、ステート図がなくなる事はありません。
特に組込ソフトウェアでOSを使わない開発をする場合は、必須と言えます。
今回は、ステート図を具体的なC/C++のコードに落とし込んで、Windowsのコンソールアプリケーションとして作ってみようと思います。
題材
不定期に入力される文字を解析して、最後にまとめて表示するようなCUIアプリを考えてみましょう。
- ユーザーから1byte文字が不定期に入力される
- 「s」が入力されてから、「e」が入力されるまでの文字を記録する
- ただし記録中に「n」が入力された場合は、次の「n」が入力されるまでは記録しない
- 「e」が入力されたら、それまで記録した文字列をキューイングして、また「s」の入力待ちに戻る
- 記録中ではないときに「e」が入力されると、キューイングされた文字列を改行区切りで画面に出力しソフトを終了する
- ユーザーからの入力に関係なく、アプリは1秒ごとに「.」を画面に出力する
設計
デザインパターンのステートパターン使うの?
使いません。現在ではアンチパターンとされています。
私も昔試してみた事はあるのですが、クラス数が多くなり見通しが悪くなるため、効率が悪くなりすぎました。
おとなしくswitch文で分岐させて作る方が一覧性が良く、効率も良いです。
ステート図
題材を満たせるようなステート図です。
状態 | 説明 |
開始文字待ち | 初期状態はこの状態になります。 「s」を待っている状態です。sが入力されるまでは何もしません。 sが入力されたら、一時的なバッファにsを格納して「入力中」状態に移行します。 |
入力中 | 「入力中」状態では、「n」または「e」以外の文字が入力されると、一時バッファにその文字を格納します。 「e」が入力されると、今まで一時バッファに貯めていた文字列を、最終出力用のキューに貯めます。 「n」が入力されると、それ以降の記録をスキップすべく「スキップ中」状態に移行します。 |
スキップ中 | 「スキップ中」状態で、「n」を受け取ると、また記録を再開すべく「入力中」状態に移行します。 この状態でも「入力中」状態と同様に、「e」を受け取ると最終出力用のキューに格納します。 |
クラス図
このステート図をコードに落とし込むべく、クラスを用意します。
文字を分解するクラスということで、Parserクラスとしました。publicなメソッドのみ書いています。
メソッド | 説明 |
Input | Inputはユーザからの文字を入力するメソッドです。ステート図の契機である「入力」に対応しています。 |
Output | Outputはバッファリングした文字列を1つ取り出すメソッドです。引数に渡したstd::stringに取り出した文字列が書き込まれます。取得できた場合はtrueを返すようにします。 |
IsIdle | 文字の分解が一通り終わり、開始文字待ち状態の時にtrueを返すメソッドです。 このソフトを終了させるときに必要な情報になります。 |
ソフトの終了
「記録中ではないときに「e」が入力されると、キューイングされた文字列を改行区切りで画面に出力しソフトを終了する」は、明らかにParserの仕事ではありません。Parserを使う上位の存在の仕事です。
ですがParserの状態がソフト終了の切っ掛けになるため、「IsIdle」メソッドを用意して間接的に知る事にしました。