- オブジェクト指向でのソフトウェア構築を例を挙げて解説
- トップダウンによる構造化との比較
例: 一般的なインタラクティブ (CUI) システム
- 航空機の予約システム
- パネルの例 (図20.1)
- セッション
- アプリケーションの開始から終わりまでの処理
- その中で状態を持ち遷移する (図20.2)
一般性と柔軟性を達成するような形でアプリケーションを構築する。特に以下に注意。
- G1 図が大きくなる可能性もある
- G2 構造は変わる物である
- 変更と追加が発生する
- G3 ここに示す仕組みは特定のアプリケーション固有の物ではない
- 流用出来ると便利である
コード例
- 構造化は全くしない
- 状態遷移は goto で行う
問題点
- ディスパッチ & goto する処理
- 制御構造がスパゲティになりがち
- 表層的な構造のままコードにしている
- 変更があるたびに中心的な構造を変更するはめになる
現実世界をモデル化する事が良いという意味ではない
- 表現が良いかどうかが重要
- この章の終わりで再度解説
処理全体を機能毎に分割・構造化するアプローチを実践してみる
「遷移処理」のコードを分離させる
- transition 関数を考える
- initial 値や is_final 関数も必要
- 遷移表 (表20.1)
各サブルーチンの構造 (図20.3)
- トップレベルに execute_session (セッションの実行)
- アプリケーションの処理の構造をそのまま表現
コード例: execute_session
- 状態の実行(execute_state)と遷移をしながら最終状態になるまでループ
- 「→」記号は引数の参照渡し
コード例: execute_state
- 状態 s に対応したパネルを表示 (display(s))
- 状態 s のパネルから入力を受け取る (read(s, →a))
- 入力処理
- 入力が受け入れられるかどうかをチェック (correct(s, a))
- 入力が受け入れられれば処理を返す (process(s, a))
- 入力が受け入れられないならエラーを返す (message(s, a))
状態の役割が広過ぎる
- 機能だけに注目すると構造が綺麗に分割されている
- しかし実際には「状態」が至るとこでたらい回しにされている
- データという観点で見ると分割されていない
データによる「復讐」
- 最初は問題無いが、後々になってデータのたらい回しによる弊害が出てくる、という意味だと思われる
- ここでは状態の内容によって異なる動きをする必要が出てくる
- 制御構造が複雑になる
- 一つの機能追加で全体の修正が必要
逆転の法則
- 「ルーチンがあまりにも多くのデータをやり取りするならば、ルーチンをデータの中に入れなさい」
- 伝統的な手続き型からオブジェクト指向設計へ転換する場合に役立つ
- 機能的な分解がされている部分を見つけるのに役立つ
クラスとしての状態
- 「状態」はクラスでなければならない
- 「状態」の特徴を表す全ての操作を含む(図20.3)
- 属性として input, choice がある
継承と暫定クラス
- execute 以外は状態によって異なる
- 継承と暫定クラスを使う
- 振るまいクラス
- 大多数に共通の振る舞いを捉えた暫定クラス
- STATE が典型的な例
完結したシステムを記述する
- STATE 以外に注目する
- すると、アプリケーションの特性が見えてくる
- execute_session, initial, is_final, transition
- 状態の登録等、その他の操作
アプリケーションクラス
- 状態の番号。これは STATE には持たせず、APPLICATIONが管理する。
- transition … 状態から他の状態への遷移を管理。二次元配列
- initial … 初期状態の番号
アプリケーションの作り方
- APPLICATION を make
- 各状態や遷移を登録
- 実行
「メインファンクション」を忘れる
- 再利用が自然にできるようになる
- ある種の自制心が必要になる
- 「このシステムは何をするのか?」と問いたい気持ちに常に抵抗する
現実の世界をモデル化する事を重要視しすぎない
- 元の goto の方が現実のモデルに近い
- 「自然」ではないが、現実にはより良い物になる
- 正しい抽象を見つけなさい