現実世界の「もの」(オブジェクト)を抽象化し、
プログラム内の構造を、このオブジェクトを基本単位に、
現実世界のアナロジーで組み立てていこうとする方針。
オブジェクト指向技術を構成する主な概念には、以下のようなものがある。
オブジェクト思考モデルは、正しく適用されれば、
以下のような多くの恩恵をもたらしてくれる。
クラスは、
オブジェクトに関する構造(インスタンス変数)、
メソッド(操作、振舞い)、
継承(親、あるいは再帰的な構造と振舞い)
の仕様である。
クラスはまた、クライアントと派生クラスに対する
アクセス許可を指定すること、すなわち
可視性と
メンバー検索の解決方法を定義する。
(a)フィールドで区別できるかもしれない
(b)もっと一般化できないか
単なる構造体ではなく、それに付随する処理を考えてみよう。
具体例、実例。オブジェクト。
クラスは、どのような機能(メソッド)と
資源(メンバ)を持つのか、という「設計図」に過ぎないのに対して、
この設計図をもとにして実際に組み立てた
もの (オブジェクト)が
インスタンスである。
また、クラス(設計図)をもとに、オブジェクト(インスタンス)を
実際に生成することを「インスタンス化」と言う。
家の設計図から実際の家を幾つでも建てられるように、
一つのクラスからは幾つでもインスタンスを生成することが出来る。
家の設計図から実際の家を建てる場合、建材や土地が必要となるように、
クラスからインスタンスを生成する時は、メモリが必要になる。
(1つのクラスから複数のインスタンスを作成する場合、
機能(メソッド/コード)の部分は全てのインスタンスで共有されるが、
資源(メンバ/変数)の分に関しては、各々のインスタンスでメモリ確保が行われる。)
クラスからインスタンス
を実際に作成する際に呼び出される、
資源(メモリ領域など)の確保の方法を記述したメソッド。
コンストラクタで確保した資源は、
デストラクタで必ず開放すること。
インスタンスが不要となる(破壊される)時に
呼び出される、確保した資源の開放などを行うメソッド。
例えば、コンストラクタで
メモリ資源を確保した場合、デストラクタではこれを全て開放する必要がある。
きちんと全て開放しない場合はメモリ・リークが生じることになる。
ガーベージコレクションを実装している
オブジェクト指向言語においては、
この厳密なメモリ開放の手続きを記述する負担が殆ど無くなる。
ひらたく言えば関数のこと。
クラスに定義されているメソッドのことを
特に「メンバメソッド」と呼ぶ。
クラスのインスタンスを object とすると、
C++言語上でのメンバメソッドは、
のような形式で呼び出せる。
メソッドは、クラス内で定義される関数または手続きで、
オブジェクトの状態の変化とメッセージ転送に関して、
オブジェクトがいかに動作し反応するかということを定義する。
メソッドが作用する対象のオブジェクトを「レシーバ receiver」と呼ぶ。
メソッドは、レシーバに対する「メッセージ message」であると考えられる。
メソッドの実行は、もし静的に決定されるなら、
「メソッド呼び出し」と言える。
もし動的に決定されるなら、
「メッセージ転送」と言った方が適切だろう。
静的に型付けされた動的束縛
(たとえばC++言語やEiffel言語)は、
まさにその両者の中間で、検査された関数ポインタを使用する。
あるクラス(設計図)を真似して、
新たなクラスを作ること。「継承」とも言う。
継承元となるクラスは「スーパークラス」「基本クラス」
と呼ばれ、継承して作られたクラスの方は
「派生クラス」と呼ばれる。
インヘリタンス(継承)は、基本クラスの機能に対して
新たな機能を追加したり、
一部の機能をカスタマイズする目的で行われ、
その他の機能に関しては基本クラスの機能は
そっくりそのまま「継承」する。
インヘリタンスは、
基本クラスの『特殊な例』を作成する目的にのみ使われるべき機能である。
一般には、継承の時に、
基本クラスの機能(メソッド)を
「何もしない」ように書き替えてはいけない。
これを許せば、極端な話、基本クラスの機能を
全部「何もしない」ように書き替えて、
新しい機能を追加することで、
全く違う目的の派生クラスを作れることになる。
このように、派生クラスが、基本クラスの抽象的な「意味」を
正しく引き継がないならば、
オブジェクト指向開発のメリットは著しく損なわれる。
しかし、「時計クラス」を継承して
「信号機クラス」を作るのは誤りである。
一部の機能は利用できるかもしれないが、
「信号機クラス」を使う利用者は、
基本クラスである「時計クラス」の機能が、「信号機クラス」では
たまに使えたり全く使えなかったりするので、イライラするだろう。
という目的のためにある機能である。
以上を要約して、基本クラスと派生クラスは
「is-a関係("…である"関係)」になっている必要がある、と言うことが出来る。
例えば、「目覚まし時計は、時計である。」「携帯電話は、電話である。」
というのは正しい。
しかし、「信号機は時計である。」「PHSは、携帯電話である。」
というのは誤っている。
つまり、PHSクラスを携帯電話クラスから継承して作成するのは
誤っていると考えなければならない。
一方、実体のない抽象メソッドを含むクラスを「抽象クラス」という。
抽象クラスがそれ自体のインスタンスを生成することはできない。
派生クラスにおいて抽象メソッドをオーバーライドしなかった場合、
その派生クラスも抽象クラスとなる。
一般に、抽象クラスは基本クラスであるし、
基本クラスは抽象クラスとして実装されることが望ましい。
継承時に、基本クラスのメソッドを
派生クラス側で書き直す(書き足す)こと。
つまり、メソッドを『再定義』すること。
オーバーライドで、基本クラスの同じメソッド名の処理を
幾つかの派生クラスの側でそれぞれ機能強化した場合、
オブジェクトの利用者は、単に同じメソッドを呼び出すだけで、
各派生クラスのオブジェクトの強化された機能を
(場合によっては特にどう強化されているかを意識する必要もなく)
使用することが出来る。
これを「多相性(多態性)」
(ポリモフィズム)と呼ぶ。
例えば、「懐中電灯クラス」には、
「スイッチを入れる」というメソッドがあるとする。
「懐中電灯クラス」から継承した「七色光線懐中電灯クラス」は、
スイッチを入れると、いくつかの色の豆電球に通電するように
機能が強化されているとする。
また、「懐中電灯クラス」から継承した「自動消灯懐中電灯クラス」は、
一定時間経過すると自動的に消灯する仕組みがあり、このため
スイッチを入れると、消灯用のタイマを開始するように
機能が強化されているとする。
しかし、「七色光線懐中電灯」にしても
「自動消灯懐中電灯」にしても、
利用者は、普通の「懐中電灯」を使う時と同じく、
単に「スイッチを入れる」というメソッドを呼び出せば良い。
一般的には、派生クラスにおいてオーバーライドされることを前提としている
基本クラスのメソッドは、抽象メソッドとして定義し、
基本クラスを抽象クラス化して
基本クラスからの直接のインスタンス化を許さない、
即ち必ず派生クラスにおいてオーバーライドするよう規定することが望ましい。
同じメソッド名で、引数の型や個数の違う
様々なバージョンを定義できる機能のこと。
つまり、メソッドを『多重定義』するということ。
オーバーライドと混同しないこと。
例として、印刷機能を考えてみる。
画面にデータを表示するprintという機能があった時、
オーバーロードが無ければ、その表示するデータの型によって
PrintString, PrintInteger, PrintFloat など、様々なメソッドを
別々に定義しなければならない。
しかし、オーバーロード機能があれば、
Printというメソッドのみを用意すれば、その引数の型ごとに
異なるメソッドを定義できるので、
クラスを使用する側が多くの名前を覚える必要がなく、
また、型が変わった場合でもプログラムの変更が少なくて済むため、
利便性が向上する。
次に処理の呼びだし関係を明確にしていくが、
このときは継承を使わず委譲で表す(実装のゼロ継承)。
そしてこれをある言語に書き下す段階になった時点で、
実装をどうするか考えるのだが、
このとき委譲を継承によって
実装することが可能である。
言語が単一継承だったなら、委譲先から一つだけを選んで
親クラスとする。どれを選ぶかが問題だが、
明らかに is-a 関係にある場合はそれが選ばれるだろうし、
曖昧な場合は、最も委譲が面倒なものが選ばれるか、
またはひとつも選ばない。
仕様だけが定義されているクラスのようなもの。
汎用的に利用できるクラス設計のキーとなる機能の集合。
主な使い方として、
「コールバックの実現」
「制御構造と制御されるものとの分離、たとえば
sortable, iterator, fetcher」
「エンジン部分とUIの分離」
『通常の継承が“実装の継承”であるのに対して、
インタフェースは“型の継承”である。』
Java で interface が言語仕様に導入されたのは、
クラスの多重継承をサポートしなかったためである。
(クラスの多重継承がサポートされていれば interface は
abstract class のサブセットでしかなくなるので、
存在価値がない。) ………というような捉え方をする人もいる。
『interface は responsibility を表す。』
interfaceは、汎化特化の関係がないクラス同士が、
何らかの約束をするためのものである。
interfaceを使うことで、クラスは、そのinterfaceにある
Methodを実装するという責任(responsibility)を負う。
クライアントから見ると、interfaceを知ることにより
相手が誰か(どのクラスを継承しているか、など)を知らずとも、
interfaceに定義されたMessageを送ることが
できるようになる。
また、このことは、継承(閉じた構造)によって実現される
多相性(ポリモフィズム)とは
別の独立な形でポリモフィズムを実現する手段を提供している。
属性や操作に対する、どのような範囲からアクセス可能かの定義。
可視性を適切に設定することによって、
カプセル化を行うことができる。
属性や操作に対する可視性には次に示す3種類がある。
外部インターフェースとしての操作に対する可視性はpublic、
実装上の操作に対する可視性はprivate か protected になる。
カプセル化のために、
内部情報となっているすべての属性に対する可視性は、
通常 private か protectedとして定義される。
すべてのクライアントからアクセス可能
すべてのサブクラスからだけアクセス可能
そのクラス自身からだけアクセス可能
「集約」は関連の特殊な形で、関連するオブジェクト間に「全体-部分」
の関係が成り立っていること言う。
この関係が特に強い集約をコンポジション composition という。
集約は’part-of’関係または包含関係として知られている。
集約関係は、あるオブジェクトが他のオブジェクトを「保有」し、
それらに対して管理責任があることを意味する。
一般的には、他のオブジェクトを有する、あるいは他のオブジェクトの一部である、
というような言い方をする。
集約関係は、一般に、集約オブジェクトがその保有者(管理者)と
同一のライフタイムを有することを意味する。
[化学]化学的に同一であるが、
結晶学的に異なった2つあるいはそれ以上の形態への結晶作用。
[動物学][植物学]いくつかの形態や
色の多様性における動物あるいは植物の存在。
(例)クラスT(図形),TA(楕円),TB(三角形)があり、
各々draw()メソッドを持つ。
TA,TBはTを継承している。
TC(真円)は、TAを継承し、draw()メソッドを持たない。
この時、以下のような文を考える。
下位クラスで同じ名前を持つメソッドの定義を
オーバーライドしていれば、そのインスタンスが所属する
クラス自身(つまり下位クラス)のメソッドが採用される。
つまり、oa.draw();では、oa の型はTであるが、
実際のインスタンスがTA型で生成されたものであれば、
TA()クラスで定義されたdraw()メソッドを呼び出してくれる。
もし動的束縛でなかったら、oa.draw()、ob.draw()
とも T型なので、Tの関数が実行される。
(このような場合、T::foo()は仮想関数であってはならない。)
システムを開発する際に予め与えておく「枠組み」のこと。
開発に先立ってきちんとしたフレームワークが出来上がっていれば
アプリケーションの開発量は少なくなり品質も向上する。
一方でフレームワークの方式設計自体に誤りがあると、
莫大な後戻り工数が発生する可能性もある。
テンプレートに問題があった場合、
アプリケーション開発グループ全体に修正を依頼することになるが、
DLLで実装された部品であれば、
うまくいけばDLLの置換だけで不具合が吸収できる。
より一般的な意味では、フレームワークは
処理の流れを抽象化したコーリング(呼び出し側)フレームワークと
個別の処理を抽象化した、いわゆるライブラリとしての
コールド(被呼び出し側)フレームワークに分けられ、
この双方が充実することにより、
アプリケーション・プログラマーは、
アプリケーションの動作目的以外の実装について
悩まされる機会が減ることになる。
プログラムを、「見栄えに関する部分」(PR層)、
「機能に関する部分」(AP層)、「データベースに関する部分」(PT層)
に分けて設計・実装しておくと、構造がスッキリし、
仕様変更に対する修正も局所化されて少ない工数で済むようになる、
という設計上のガイドラインまたは経験則。
プログラムの修正の多くは、文字の色や、画面部品の配置座標など、
PR層に集中する。
このため、AP層やPT層に影響を与えることなく、
PR層の部品だけを変更できるようにしておくことは特に重要である。
逆に、データベース(PT層)のレコードレイアウトに仕様変更が発生し、
データの取得や計算方法が変化しても、
AP層でその変更を吸収すれば、PR層のプログラムには
一切手を加える必要はなくなることも多い。
単純な概念ではあるが、
プロジェクトで意識してこのガイドラインを守るか否かで、
(せっせと物件を製造している段階では違いはあまり分からないが)
検証や保守段階になって修正が多く発生するようになると
その効果が顕著に表れてくる。
見栄えを実現する部品群の概念。
機能を実現する部品群の概念。
不揮発データを実現する部品群の概念。
その実装は通常データベースであるため、DB層とも呼ばれる。
ガーベージ・コレクションは、いくつかのアプリケーションでは
受け入れがたいオーパヘッドになる可能性もある。
しかし、手作業での記憶域の解放のオーパヘッドは、
たぶん、自動的なガーベージ・コレクションと同じくらい
高価であると考えられる。
[参考]マーク&スイープ保守的GC