Android入門―本気で使える電卓アプリの開発―3

電卓の基礎ロジックの作成

最初に電卓の基礎部分を作ります。 単にA+B結果を表示するだけなのですが、 0から9の数値ボタン、四則演算ボタン、イコールのボタンとそれぞれ動作を割り当てなければなりません。 さらに今Aを入力中なのかBを入力中なのか、演算結果を表示中なのかを判定して各ボタンの動作を決めていかないといけないのです。簡単そうに思えて以外に複雑なんですね。

通常のプログラミングなら、このようなアプリケーションは状態をフラグで管理してifやcaseによる分岐を延々とコーディングしていくことになります。状態や動作が増えるにつれフラグは増え分岐は巨大となり収拾がつかなくなってしまいます。機能追加ともなれば全てのifやcase文に手を加える作業が待っています。これは大変です。

今回はデザインパターンを使用してifやcaseによる条件分岐を排除し、見通しのよい構成にしてみたいと思います。

GoFのデザインパターン

GoFのデザインパターンとはオブジェクト指向言語においてよく使われる設計の「パターン」であり「ノウハウ」です。プログラム設計の定石なのです。

今回、電卓の基礎部分では、SingletonパターンとStateパターンを使用します。パターンを使用することで、再利用しやすく、機能拡張しやすい構成になります。

Stateパターン

これは状態に対して動作を規定する場合に最適なデザインパターンです。Stateパターンでは状態をクラスで表します。状態が遷移している様子はクラスを差し替えて表現します。

Singletonパターン

これはそのクラスのインスタンスが1つしか生成されないことを保証するためのクラス設計です。 状態を表すクラスは1つだけで良いのです。もし状態が変わるたびにクラスを作成してしまったなら無駄なメモリを使ってしまいますので、Singletonパターンを用いて、状態を表すクラスはたった1つだけ生成されることを保証しましょう。

オートマトン理論と状態遷移表

遷移するルールと状態が遷移したときに何をするのかを定義しているものを有限オートマトンと呼びます。 それを図で表現したものが状態遷移図です。同じく表で表現したものを状態遷移表と呼びます。

電卓のオートマトンと状態遷移表

電卓には色々なボタンがありますね。これらは外部からの入力(イベント)を受け付るものであると考えます。イベントには次のようなものが考えられます。

  • イベント1)0~9のボタンが押される
  • イベント2)四則演算のボタン(×÷+-)が押される
  • イベント3)=のボタンが押される

そのほか、%のボタン、クリアボタンなどがありますね。これらもすべてイベントです。 電卓はこれらイベントの発生によって次々に状態が変化するわけです。

次に電卓の状態を考えましょう。電卓の場合、次の状態(State)が考えられます。

  • 状態1)数値Aの入力中
  • 状態2)四則演算の選択(×÷+-)
  • 状態3)数値Bの入力中
  • 状態4)演算した結果を表示中

次に電卓の動作です。電卓の基本動作になります。

  • 動作1)数値Aを保持する
  • 動作2)数値Bを保持する
  • 動作3)押された四則演算のボタンを保持する
  • 動作4)「数値A (× or ÷ or + or -) 数値B」を計算して表示する

このような状態と動作の紐付け、そして状態の遷移先を表にまとめると次のようになります。

図:状態遷移表
電卓 状態遷移表 状態 State
A:数値入力状態A NumberAState C:演算モード OperationState B:数値入力状態B NumberBState R:結果表示状態 ResultState
イベント event 0,~,9 入力をディスプレイに反映 →A ディスプレイクリア 入力をディスプレイに反映 →B 入力をディスプレイに反映 →B ディスプレイクリア 入力をディスプレイに反映 →A
四則演算 ÷×-+ ディスプレイを変数Aへ 演算子記憶 →C 演算子記憶 →C ディスプレイを変数Bへ反映 「変数A 演算子 変数B」をディスプレイ表示 ディスプレイ表示(演算結果)を変数Aへ 演算子記憶 →C 「変数A 演算子 変数B」をディスプレイ表示 ディスプレイ表示(演算結果)を変数Aへ 算子記憶 →C
入力中の値を表示 →R ÷,×の場合、「変数A 演算子 変数A」をディスプレイ表示 +,-の場合、変数Aをディスプレイ表示 →R 「変数A 演算子 変数B」をディスプレイ表示 →R なにもしない →R
Clear 変数Aクリア ディスプレイクリア →A 変数Aクリア ディスプレイクリア →A 変数Bクリア ディスプレイクリア →B 変数Aクリア 変数Bクリア ディスプレイクリア →A
AllClear 変数Aクリア 変数Bクリア ディスプレイクリア →A 変数Aクリア 変数Bクリア ディスプレイクリア →A 変数Aクリア 変数Bクリア ディスプレイクリア →A 変数Aクリア 変数Bクリア ディスプレイクリア →A

状態遷移表が出来たならまあほとんど完成したも同然ですね。あとは各状態を、Stateインタフェースを実装したクラスで表現していきます。

クラス内にはその状態の場合にイベントが発生した場合の動作を記載しておきます。 状態遷移表の各マスに記された動作の実装はContextに定義し、必要に応じてStateからContextに委譲します。

そして電卓クラスはContextを実装します。電卓には状態を保持しますので、Stateクラスを持ちます。 ではソースを見ていきましょう。

全ソースのダウンロードはこちらです。

まずStateインタフェースです。

Stateでは、電卓に対するイベントを処理します。イベントは「数値ボタン」「四則演算ボタン」「=ボタン」「クリアボタン」「オールクリアボタン」の5つを規定しています。

ではこれを実装する数値入力状態A(NumberAState )の実装をみてみましょう。

最初の部分である、

が、Singletonパターンになります。よく見るとコンストラクタがprivateになっています。こうすることでクラスのインスタンスを新たにNewできなくなります。じゃあどうやって利用するのかというと、唯一のインスタンスを取得するためにgetInstanceメソッドを呼びます。

この単純な仕組みにより、クラスのインスタンスが唯一つであることを保証しているわけです。

では、電卓のイベントの実装です。

onInputNumberメソッドでは数値ボタンが押されたときのイベントを処理しています。contextのaddDisplayNumberメソッドを呼び出して、押された数字をディスプレイに追加します。
数値ボタンが押された場合、次の状態も「数値入力状態A(NumberAState )」ですので状態遷移はありません。

次は四則演算のボタンが押された場合の処理です。状態遷移表には

ディスプレイを変数Aへ
演算子記憶
→C

と記載されていますのでその通り実装していきます。

context.changeState(OperationState.getInstance())で、次の状態に遷移しています。四則演算のボタンが押された場合は、状態C:「演算モード(OperationState)」に遷移します。

その他の状態も実装してきます。

Stateを実装するクラスの各イベントメソッドでは、contextのメソッドを状態にあわせて呼び出しています。このcontextは電卓の基本機能そのものです。

メインとなる電卓クラスはContextを実装します。これらは各Stateからのコールバック関数となります。実際の挙動については電卓クラスに実装して、各StateからはContextに記載された動作を呼び出すように実装します。

また、電卓は唯一の出力デバイスとして液晶ディスプレイを持ちます。このディスプレイはクラスで表現しましょう。これにより、ディスプレイを差し替えて電卓の機能アップが容易に行えるように準備しておくためです。今回は単にテキストで表示する12桁のディスプレイを想定しましたが、後にグラフィックを用いたディスプレイクラスへと変更するのも面白いでしょう。

では、電卓クラスです。

いかがでしょう。メインとなる電卓クラスにはごちゃごちゃとしたIF文やCASE文も無く、実にすっきりしていますね。イベントが発生したら、stateのメソッドを呼び出しているだけです。あとは状態にあわせた適切な処理をstateが選択し実行してくれるという感じです。

電卓の液晶ディスプレイに数値を表示するStringDisplayは、内部に12桁の文字列を保持するクラスです。表示に関する処理はほとんとStringDisplayに委譲しています。StringDisplayの実装はダウンロードしたサンプルの中にありますので参考にしてください。ポイントは12桁の文字列をスタックで保持している点です。電卓の入力は後入れ先出し型のスタック形式なのです。スタックで保持することにより、今後BS(バックスペース)ボタンを追加したとき、「液晶ディスプレイに表示された数字を後ろから1つ削除する」といった操作に簡単に対応できることを狙っています。

さて、電卓クラスの唯一の機能である演算ですが、電卓クラスのdoOperationメソッドに実装されています。でも実際の演算らしきものが一切ありませんね。

これは四則演算処理を列挙型で定義しているからです。javaの列挙型はその実態はクラスですので、処理を実装することも出来るわけです。これによりどの演算子であってもop.eval(A, B)という記述で四則演算ができてしまいます。 列挙型Operationのソースです。

列挙型でメソッドの抽象を宣言し、定数ごとに具象メソッドでオーバーライドしています。そのようなメソッドを「定数固有」メソッドと呼びます。やや複雑な仕組ですが、知っておくととても便利です。

これで電卓の基本部分の説明は終わりです。

さてなかなかAndroidの話に入れませんね、電卓のベース部分が完成しましたので、次回からお待ちかねAndroidへの実装を行います。あと暫くお付き合い下さい。

参考文献

増補改訂版Java言語で学ぶデザインパターン入門

StateパターンでCSVを読む

デザインパターンの使い方: State

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です