オブジェクト指向ソフトウェア開発とは
0. オブジェクト指向ソフトウェア開発
- オブジェクト指向開発方法論に則ってシステムを分析/設計し、その結果をオブジェクト指向プログラム言語によって実現することである
すなわち、問題領域に現れるモノ(オブジェクト)をそのままモジュール単位(クラス)としてとらえ、それを基準にプログラムを開発することである
1. クラス
- クラスはデータメンバとメンバ関数で構成される
- データと処理を一組にしてまとめることで、プログラムが管理しやすくなる
- クラスは型である
- クラスはユーザーの視点からシステムを客観的に見たときに、そこに存在する対象をモジュール化したものである
- クラスの作り方、メンバ関数の切り分け方がオブジェクト指向プログラミングの肝である
2. メンバ関数
- メンバ関数はクラスのデータメンバの処理を専門とする、そのクラス専属の関数である
- データメンバに対する処理は、基本的にすべてメンバ関数を経由させる
- データメンバを隠蔽することで、意図しないデータの改変や領域破壊を根絶する
- 将来的な改変に備えて、予めvirtualを付けて定義しておく(仮想関数)
3. オブジェクト指向での開発
-
優れたソフトウェアとはユーザーの要求を満たし、フィードバックを得ながら成長し続けるもの
つまり、開発時ではなく運用時の視点を重視する
オブジェクト指向で開発することで、システムに高い保守性と拡張性を持たせることができる -
オブジェクト指向開発の手順
- システム分析
- そのプログラムが何をしなければいけないのか、システムに何が求められるのかを整理する
- クラスを洗い出してクラス名、操作、関連を定義し、その後に操作のために必要な属性を考える
- この段階では、問題領域のクラスのみを考える
- 問題領域はシステムが解決しようとしている現状の問題(コンピューター領域と対の概念)
- この段階では、実行環境は考慮しない
- システム設計
- 分析結果をもとに、ソフトウェアとして実行するための実装方法を考える
- コンピューター領域のクラスについても考える
- コンピューター領域は画面やボタンなど、コンピューターを使った問題解決の手段である
- システム分析工程との明確な線引きは不要である
- 実装
- コーディング、テスト、デバッグ
- 上記1~3の工程中にクラスの静的、動的モデリングの修正を繰り返す
- 静的モデリング
- システムが「どのようなオブジェクトで構成されているか」を分析/設計する
- 動的モデリング
- システムで「どのようにオブジェクトが動作するのか」を分析/設計する
- 静的モデリング
- システム分析
静的分析
4. クラスの抽出
- オブジェクト指向の考え方は、シミュレーション用に開発されたオブジェクト指向言語Simulaが元になっている
- シミュレーションとは、現実に行われていることをコンピューター上で仮想的に行うことである
- クラスを探し出す作業は、システムの仕様から名詞を探し出す作業である
- クラス図の作成
- クラスの洗い出し
- 関連の探索と操作の洗い出し
- 属性の洗い出し
- ある操作がそのクラスに属するべきかは、「それを誰がするのか」、その操作の主語を考えて判断する
- 問題領域(分析段階)のクラス同士は対等な関係であり、属性に他のクラスを持たせることはしない
- コンピューター領域(設計段階)のクラスは他のクラスの属性になることがある
- クラスの本質的な特徴でないものは属性ではなく関連であり、関連クラスとして別けるべき場合が多い
- ある属性を関連として切り出すかや、あるクラスを属性として統合するかは
アプリケーションの問題領域によって異なるため、柔軟に判断する必要がある
5. 関連
- オブジェクト指向のプログラムはクラスがクラスを操作する
- 関連はクラス間にどのようなやり取りがあるのかを表現するものである
- クラス図
- クラス間に関連(操作)を記載する
- 各関連には多重度を記載する
- 必要に応じてロール(関連相手のクラスに対してどのような位置づけか)を記載する
- メンバ関数の引数などの一時的な関連もクラス図には記載しておく
- 実装
- 関連先が単一の場合、基本的にはメンバとして関連先クラスのポインタを持つ
- 関連先が複数の場合、基本的にはメンバとして関連先クラスのポインタ配列を持つ
- 関連にはクラス同様、ときには属性や操作が存在する
- クラス同士の多重度が限定できない場合は関連をクラス化し、クラス間を繋ぐことで対応できる
- 関連先が複数になると属性も複数必要になる場合、その属性は関連の属性としておくべきものである
- 例えば「課」と「社員」クラスを考えたとき、「課員コード」属性は「社員」ではなく関連クラス「課員」の属性とする
- クラス同士の多重度が限定できない場合は関連をクラス化し、クラス間を繋ぐことで対応できる
6. 継承
-
継承は他のクラスの属性/操作を借りてきて、自クラスと合成して1つのインスタンスを生成する仕組みである
-
継承はプログラムを作らないためのテクニックである
-
継承には「is-a」の関係性がある
-
継承の目的にはクラスの汎化と特化の2種類がある
- 汎化は基底クラスを作ることである
- 特化は派生(導出)クラスを作ることである
- 汎化のためにアドホックカテゴリーの概念(限定的な目的の一時的な概念)をクラスとする場合がある
-
オーバーライド(重複定義)により、基底クラスを触らずにカスタマイズすることができる
- オーバーライドを前提とした、処理内容の無いメンバ関数を純粋仮想関数とすることができる
- 純粋仮想関数を持つクラスは抽象クラスとなり、インスタンス化されることを防止できる
-
継承時の親クラスのアクセス指定子は通常publicにする
-
クラスアクセス指定子(参考)
継承方法 基底クラスの public
メンバー基底クラスの protected
メンバー「is-a」関係 public
継承public
のままprotected
のまま成り立つ protected
継承protected
に変わるprotected
のまま条件付きで成り立つ private
継承private
に変わるprivate
に変わる成り立たない
7. より高度な分析
クラス属性
- クラス属性とはインスタンスごとではなく、クラスごとに持つ属性のことである
- 実装ではstaticデータメンバとなる(コンパイル時に1領域に確保される)
- クラス属性はインスタンス単位で存在しないため、コンストラクタで初期化してはいけない
- クラス属性の使用例
- インスタンス数の把握(解放漏れの確認や特定の数にしたい場合など)
- クラス単位のエラーを集約して記録する
派生属性
- 派生属性とは実際は存在していないのに存在しているように見える属性(DBの導出項目みたいなの)
- 例えば「年齢」という属性は「生年月日」とOSの現在日時から求められ、
通常の属性としてしまうと手動更新が必要になってしまうため、クラスの分析時に明確に派生属性としておく
- 例えば「年齢」という属性は「生年月日」とOSの現在日時から求められ、
- 継承の「派生」とは無関係
集約
- 集約とは「全体-部分」、「a part of」の関係である
- 集約では全体から部分、または部分から全体への「伝搬」を必要とするような処理が行われる
- 集約の実装では、全体クラスのメンバに部分クラスの実体を持つ(ポインタではなく)
- 下流クラスのインスタンスの寿命は、上流クラスのインスタンスの寿命に完全に支配される
8. 多重継承
- 多重継承とは基底クラスを複数もつ継承のことである
- クラスが複数のis-a関係を持つ場合に用いられる
- クラスAから2つのクラスB, Cが派生し、B, Cを多重継承した1つのクラスDを作成すると、DはAのメンバを2重に持ってしまう
- Aを仮想基底クラスに指定することで上記の問題を回避できるが、
仮想基底クラスには様々な問題が残るため、Mixinなど抽象的な基底クラスの作成で解消できないか検討する
- Aを仮想基底クラスに指定することで上記の問題を回避できるが、
動的分析
9. オブジェクトの状態
- 一般的にグローバル変数はプログラムの状態を管理するために用いられる
オブジェクト指向開発において、データメンバはクラスにとってのグローバル変数であり、状態といえる - 状態はイベントによって変化する
オブジェクト指向開発において、イベントはメンバ関数で実装する - クラスごとの状態遷移図を作成することで、クラスがとり得る状態について網羅的に把握することができ、
想定外の状態に起因するバグを未然に防ぐことができる
10. より高度な状態管理
- イベントは一瞬しか存在できない
- 例えば「ボタン押している間」というイベントは「押したとき」と「離したとき」として整理する
- 入退場動作
- ある状態に入る際に必ず行われる動作を入場動作という
- ある状態から抜けるときに必ず行われる動作を退場動作という
- protectedメンバ関数として実装する
- 内部動作
- 状態の遷移を伴わない、ある状態の内部で行われる動作
- 入退場動作を伴わない
- protectedメンバ関数として実装する
- 自己遷移
- 遷移元と遷移先が同じ状態遷移のこと
- 入退場動作を伴う
- 状態の入れ子
- 複数の状態に共通する性格をグループ化し、それらの状態の「スーパー状態」として切り出すことができる
- 「スーパー状態」の性格はそこに含まれるすべての状態(サブ状態)にも適用される
- あるスーパー状態からスーパー状態に遷移し、その後に元の状態に戻るとき、戻り先はサブ状態になる
- 例えば「電源オン」というスーパー状態から「電源オフ」状態に遷移した後、
元に戻るときは「電源オン」状態ではなく、そのサブ状態である「停止中」状態とするべきである
- 例えば「電源オン」というスーパー状態から「電源オフ」状態に遷移した後、
- スーパー状態、サブ状態それぞれを独立した属性として実装する
- 状態は並列する
- 1つのクラスが複数の状態を持つことは自然なことである
- 各状態を独立した属性として実装する
- 条件付き状態遷移
- 同じイベントが起こってもそのときの条件によって遷移先が変わることや、イベントが無視されることがある
- 静的分析との関係
- 動的分析によって得られた結果は静的要素の一部となり、静的分析での見落としを補う
11. メッセージシーケンス
- メッセージシーケンスとは、どのような操作が、どのような順序でクラス間で行われるのかを分析することである
- 分析には「シーケンス図」や「コラボレーション図」を用いる
- シーケンス図は時間軸に沿ったシステムの動作の分析に適している
- コラボレーション図はシステムの全体的な構造の把握に適している
- メッセージシーケンスの分析は静的分析がある程度完了した段階から行う方が良い
- 静的分析とメッセージシーケンスの分析の結果に齟齬が発生した場合、まずメッセージシーケンスの修正を検討する
- システムの動作に合わせて、無理矢理クラス同士の関係を変更しないよう注意する
- システムの主な処理に対して行えば十分
- 内容が把握しづらくなったり、メンテナンスが煩雑になったりしない程度に分析を行う
設計・実装
12. オブジェクト指向設計
- オブジェクト指向においてクラスは、分析から実装まで一貫して存在する、ソフトウェアの構造の基幹をなす単位である
- 分析を経て設計に入ったら、システムを動かすために必要なコンピューター領域の概念についても検討する
- 問題領域とコンピューター領域のクラスの分類には、Doc/View、MVC、OOSE等の考え方がある
- 一例としてのOOSE(Object-Oriented Software Engineering)でのクラス分類
- 実態クラス
- 問題領域のクラス
- インターフェースクラス
- システムのオペレーターの接点となり、入出力を司るクラス
- 制御クラス
- システムが動作するうえで現実的に必要になる上記以外のクラス
- 実態クラス
- より実践的な分類
- ユーザーインターフェースなどを司る、問題領域とオペレーターの間を介在するクラス
- 問題領域のクラスインスタンスの属性をオペレーターに提示し、それらに対する操作の窓口になる
- オペレーターが直感的に理解できるインターフェースとする
- どの属性を
- どう操作するのか
- ある特定の処理を専門に行う、関数のようなクラス
- 問題領域のクラス等が受けた操作を、委譲されるクラス
- 委譲のメリット
- 問題領域の各クラスに複雑な処理を持たせる必要がなくなる
- 特定の処理に特化したクラスを使い回すことができる
- 問題領域のクラスを拡張し、そのアプリケーションに特有の処理を追加したクラス
- 他クラスとの関連によってクラスの再利用性が損なわれる
- アプリケーションの仕様を問題領域のクラスに持ち込む場合は、
クラスを派生することで元のクラスの改変を避けられる- 分析段階で洗い出された問題領域のクラスはそのままに、
設計段階でシステムを動作させるための派生クラスを作成する
- 分析段階で洗い出された問題領域のクラスはそのままに、
- ユーザーインターフェースなどを司る、問題領域とオペレーターの間を介在するクラス
- クラスの詳細化
- 操作の詳細化
- 動作に必要な情報やその型、アクセス指定子について決定する
- 属性の詳細化
- 型を決め、基本的にアクセス指定子はprivateとする
- 問題領域と異なり、コンピューター領域のクラスは他クラスの属性になる場合がある
- 関連の詳細化
- 関連が単方向なのか、双方向なのかを検討する
- 関連を張ると、関連相手のクラス無しでは動作できなくなり、独立性は失われる
- 仕様に合わせて適切な関連の実装を検討する
- 参照関連(ポインタ保持)
- 保有関連(実体保持)
- 使用関連(引数)
- その他のアルゴリズム(リスト、二分木、スタック、キューなど)
- 関連が単方向なのか、双方向なのかを検討する
- 操作の詳細化
13. クラスライブラリ
割愛
14. 実例
割愛
参考
Tucker! 憂鬱なプログラマのためのオブジェクト指向開発講座