オブジェクト指向プログラミング

OOP言語の系譜(水色がOOP)
オブジェクト指向プログラミング(オブジェクトしこうプログラミング、英: object-oriented programming; OOP)は、オブジェクト指向の考え方[1]を取り入れたコンピュータ・プログラミング技法である。プログラムを「オブジェクト」の集合体として構築し、オブジェクトはデータ(状態)を主とし、コード(動作)を従として構成され、無数のオブジェクトが相互に作用し合うようにして任意の処理が実行される。オブジェクトとは対象物を意味し、CPU演算処理(コード)が参照ないし変動の対象とするデータ全般と考える事が出来る。「どの様にデータを扱うか」ではなく「データがどの様に扱われるか」を念頭に置いてプログラムを組み立てる手法がOOPという事になる。
オブジェクト指向プログラミングは、ソフトウェアの大規模化に伴い、より効率的な開発手法が模索される中で1960年代から70年代にかけて確立された。OOP言語は歴史的にクラス仕様を中心とするSimula系統と、メッセージ仕様を主体とするSmalltalk系統に分けられるが、後にC++やJavaなどの言語の派生元となった前者が主流となった。この前者のオブジェクト指向が示す「カプセル化」「継承」「多態性」の三大原則を備えていれば、OOP言語であると見なされる。
目次
1 特徴
2 歴史
3 OOP言語一覧
4 OOP言語の仕組み
4.1 オブジェクトの概念と実装
4.1.1 オブジェクトの実装構造
4.1.2 クラスのオブジェクト化
4.1.3 プライベートデータの扱い方
4.1.4 コンポジション
4.2 メッセージ
4.3 クラス
4.3.1 クラスベース
4.3.2 プロトタイプベース
4.4 データメンバ
4.4.1 インスタンスデータメンバ
4.4.2 クラスデータメンバ
4.5 メソッド
4.5.1 クラスメソッド
4.5.2 システムメソッド
4.5.3 コンストラクタとデストラクタ
4.6 ガベージコレクション
4.7 アクセスコントロール
4.8 脚注
5 関連項目
特徴
OOPの原則(fundamental principle)仕様は以下の通りである。ここでの原則とは何を以ってOOPと見なすべきかを示す規定を意味している。1~3はSimula系統であるクラス主体(classes-principled)OOPの三大原則となっている。4はSmalltalk系統であるメッセージ主体(messaging-principled)OOPの土台とされる。
カプセル化 (encapsulation) - 内部隠蔽
継承 (inheritance)
多態性 (polymorphism)
多重定義 (overloading) - 演算子オーバーローディングも含む
パラメータ多相 (parametric polymorphism) - ジェネリックプログラミング
派生型付け (subtyping) - 動的なもののみ
仮想関数 (virtual function)
多重ディスパッチ (multiple dispatch)
メッセージパッシング (message passing)
- カプセル化
従来の多くのプログラミング環境では、「動作」を定義するコードが、「状態」を表現するデータを扱う形でプログラムを記述するのが一般的だったが、開発規模の拡大に伴い、解決の難しいソフトウェア・バグ原因の大半は「データの予期せぬ変動」と「各データ間の不整合」である事が経験則で知られるようになって来た。そこでデータを中心にしてプログラムを組み立てようとする考え方が生まれ、データ群とそれに付随するコード群をまとめたオブジェクトというプログラミング概念が編み出された。従来の「コードの為のデータ」とは逆に「データの為のコード」としたのは、データを変動させたコード位置を容易に特定出来るようにしてバグ発見に繋げる為であった。この目的に沿って特定のデータを扱えるコード範囲を厳密に定めた内部隠蔽の機能は、アクセス範囲外からのデータ参照とコード呼出をコンパイルレベルで禁止して、想定外のデータ変動をもたらす人的ミスを減らした。
- 継承
また、プログラム内の膨大な数のデータは通常グループ化(構造体)されて扱われていたが、各構造体間にまたがる共通のデータ集合が多数出現し、その冗長性と整合の頻雑さの問題が浮き彫りとなっていた。これを解決する為にOOPは、構造体=オブジェクトを複数の階層要素に分け、共通のデータ集合を親階層とし、派生する子階層に親階層の参照アドレスを持たせて連結する構造とした。A階層から成る親オブジェクトから派生した子オブジェクトはA+B階層として構成され、これが継承と呼ばれた。コードから参照されたデータがB階層に無い時は、次のA階層に有るか探す仕組みとなり、この連鎖によって傍からは一つのオブジェクトとして存在した。なお、派生クラスで任意のデータとメソッドを追加出来る継承の機能は、工夫次第でコードの再利用性を高めるとも見なされたが、深い継承がもたらす構成把握の難化がネックとなってほぼ否定される様になり、クラスライブラリ使用の範疇内に留まっている。現在[いつ?]では深い継承は回避される様になり、代わりに共通メソッド呼出テーブル(Javaのinterfaceなど)を実装させる横に長い継承が主流となっている。また、Simula系統のOOP言語では多態性を実現する為の重要な手段として用いられている。
- 多態性
様々なオブジェクトタイプ(型)が扱われるソースコード記述の頻雑性を回避する為に、呼出引数の型に応じたメソッドをコンパイル時に選択する多重定義と、型引数に応じたクラスorメソッドをコンパイル時に生成するパラメータ多相の機能が備えられた。これらは静的な多態性(static polymorphism)とも呼ばれる。動的な多態性(dynamic polymorphism)としては、特定の場面で扱われるオブジェクト群が共通して持つ階層要素の部分で一括りにして順次処理を行い、条件分岐をより柔軟かつ簡素にする目的で、特定の階層のコードの存在を抽象化して呼び出しポイントとし、オブジェクト共通の階層要素が持つ呼び出しポイントから、それぞれ異なる派生先階層の実装コードを呼び出すという仮想関数の仕組みが実装された。また、オブジェクトが特定の階層要素を持つかチェックした上でその階層に準じた動的な派生型付けを行い、その型変化させたオブジェクトを引数にした多重定義と仮想関数呼び出しの併せ技である多重ディスパッチの機能も編み出され、データがコードを制御するというOOPの理念は明確に表現された。
- メッセージパッシング
上述の三つの機能と異なり、メッセージパッシングはオブジェクト指向そのものと肩を並べるパラダイムであり、オブジェクト指向が示す機能の大半を結果的に実現出来る「メタ」的な土台となった。これは従来のサブルーチンコールの形態を変えたもので、基本はバイトデータ(メッセージ)の送受信でメソッド名とパラメータ値、そしてリターン値をやり取りする仕組みだった。オブジェクトのレシーバー関数が引数として渡されたメッセージを読み込み、オブジェクト内部でそれに準じた処理を行い、結果をリターンした。レシーバーの仕組みは結果的に「内部隠蔽」を実現出来た。オブジェクト内部での自由自在な処理実装によるリターンはこれも結果的に「多態性」を実現出来た。この実装をサポートする為にクラス構造定義を操作できる「リフレクション」の機能が備えられた。メッセージパッシングがもたらす変幻自在なメソッドの選択実行は、通信網を介して他サーバー上で実行されているサービスプロセスを自身のソフトウェアの一部として扱う様な事も可能にした。あらゆる手法に応用出来るメッセージパッシングは、その適用範囲を広げるほど開発工数と実行負荷も増大するので、決してオールマイティに用いられるものではないが、ソフトウェアのモジュール化およびコンポーネント化をよりダイナミックに、そしてグローバルに発展させる潜在力を秘めており、OOPがもたらす実装の拡張性を大きく広げた。
歴史
オブジェクト指向プログラミングという考え方が生まれた背景には、計算機の性能向上によって従来より大規模なソフトウェアが書かれるようになってきたということが挙げられる。大規模なソフトウェアが書かれコードも複雑化してゆくにつれ、ソフトウェア開発コストが上昇し、1960年代には「ソフトウェア危機 (software crisis)」といったようなことも危惧されるようになってきた。そこでソフトウェアの再利用、部品化といったようなことを意識した仕組みの開発や、ソフトウェア開発工程の体系化(ソフトウェア工学 (software engineering) の誕生)などが行われるようになった。
このような流れの中で、プログラムを構成するコードとデータのうち、コードについては手続きや関数といった仕組みを基礎に整理され、その構成単位をブラックボックスとすることで再利用性を向上し、部品化を推進する仕組みが提唱され構造化プログラミング (structured programming) として1967年[要出典]にエドガー・ダイクストラ (Edsger Wybe Dijkstra) らによってまとめあげられた(プログラミング言語の例としてはPascal 1971年)。なお、それに続けて「しかしデータについては相変わらず主記憶上の記憶場所に置かれている限られた種類の基本データ型の値という比較的低レベルの抽象化から抜け出せなかった。これはコードはそれ自身で意味的なまとまりを持つがデータはそれを処理するコードと組み合わせないと十分に意味が表現できないという性質があるためであった。」といったように、ほぼ間違いなく説明されている。
そこでデータを構造化し、ブラックボックス化するために考え出されたのが、データ形式の定義とそれを処理する手続きや関数をまとめて一個の構成単位とするという考え方でモジュール (module) と呼ばれる概念である(プログラミング言語の例としてはModula-2 1979年)。しかし定義とプログラム内の実体が一対一に対応する手続きや関数とは異なり、データはその形式の定義に対して値となる実体(インスタンスと呼ばれる)が複数存在し、各々様々な寿命を持つのが通例であるため、そのような複数の実体をうまく管理する枠組みも必要であることがわかってきた。そこで単なるモジュールではなく、それらのインスタンスを整理して管理する仕組み(例えばクラスとその継承など)まで考慮して生まれたのがオブジェクトという概念である(プログラミング言語の例としては1967年のSimula 67)。
Simulaのオブジェクトとクラスというアイデアは異なる二つの概念に継承される。一つはシステム全てをオブジェクトの集合と捉え、オブジェクトの相互作用をメッセージに喩えた「オブジェクト指向」である。オブジェクト間の相互作用をメッセージの送受と捉えることで、オブジェクトは受信したメッセージに見合った手続き単位(≒関数)を自身で起動すると考える。結果オブジェクトは自身の持つ手続きのカプセル化を行うことができ、メッセージが同じでもレシーバオブジェクトによって行われる手続きは異なる――多相性(ポリモーフィズム)を実現した(このメッセージを受け実行される手続き単位は、メッセージで依頼されたことを行うための「手法」の意味でメソッドと呼ばれる)。この思想に基づき作られたのがSmalltalk(1972年)であり、オブジェクト指向という言葉はこのときアラン・ケイによって作られた。
一方、Smalltalkとは別にSimulaの影響を受け作られたC++(1979年)は抽象データ型のスーパーセットとしてのクラス、オブジェクトに注目し、オブジェクト指向をカプセル化、継承、多相性をサポートするものと再定義した(その際、実行時速度重視及びコンパイラ設計上の制約により、変数メタファである動的束縛の特徴は除外された)。これらは当初抽象データ型、派生、仮想関数と呼ばれ、オブジェクトのメンバ関数を実体ではなくポインタとすることで、継承関係にあるクラスのメンバ関数のオーバーライド(上書き)を可能にしたことで、多相性を実現した(この流儀ではメッセージメタファはオブジェクト指向に必須ではないものと定義し、オブジェクトの持つ手続きをメソッドとは呼ばずメンバ関数と呼ぶ)。この他、Smalltalkにある動的束縛の類似的な機能としてオーバーロード(多重定義)が実装されている。
Smalltalkはこの「全てをオブジェクトとその相互作用で表現する」というデザインに立ち設計されたため、全てをファイルと捉えるファイル指向オペレーティングシステムからの脱却と、プログラムをフロー制御された手続きと捉える手続き型言語からの脱却が行われた。そのためSmalltalkは自身がオブジェクト指向オペレーティングシステムでもあること、メッセージ・パッシングなどの特徴を持った。これは当時のプログラム言語としては特異的であり、ガベージコレクタを必要としインタプリタ形式で実行される処理の重さも手伝って先進的ではありながら普及しがたいものであると捉えられた。また、メッセージでの多相性は、変数へのオブジェクトの動的束縛が前提となるため、静的型チェックが必要な実行時性能重視のコンパイラ言語にとって、実装から除外すべき特徴となった。
C++の創始者ビャーネ・ストロヴストルップは、Smalltalkのような論理美の追求よりも、現用としての実用性を重視した。そのため、C++の再定義した「オブジェクト指向」はこれらの問題を全てクリアにし、既存言語の拡張としてオブジェクト指向機能を実装できることでブレイクスルーを迎え急速に普及する。これによりメッセージ送信という考え方はやや軽視されるようになり、オブジェクト指向とはC++の再定義したものと広く認知されるようになった。
1980年代後半に次々と生まれたオブジェクト指向分析・設計論は、Smalltalkを源流とするオブジェクト指向を基に組み立てられた。このときSmalltalkは健在であったが広く普及しているとは言えずC++で実装する機会が多かったが、C++はSmalltalkとは思想的にやや異なることと、仕様の複雑さが問題とされた。このニーズを受けC++の提示した現実解と、Smalltalk的理想論を融合するものとして[要出典]、構文面ではシンプル化しながらも強くC++の影響を受けつつ、一方で用語や思想面でSmalltalk色を濃くした[要出典]Javaが作られた(1995年に初期リリース)。バランス感覚に長けたJavaの登場によってオブジェクト指向開発に必要な要素が全てそろい、1990年代後半からオブジェクト指向は広く普及するようになった。
OOP言語一覧
オブジェクト指向を総体的または部分的にサポートする機能を備えたプログラミング言語の公開は、1980年代後半から顕著となった。オブジェクト指向プログラミングのスタイルは、クラス仕様を中心とするSimula系統と、メッセージ仕様を主体とするSmalltalk系統の二通りに分けられる。前者には「C++」「Java」「C#」などが、後者には「Smalltalk」「Objective-C」「Swift」などが挙げられる。
オブジェクト指向プログラミング言語の形態には、オブジェクト指向と他のパラダイムが同居しているハイブリッドOOPと、オブジェクト指向に基づくコードプロセスだけが許容されるピュアOOPの双方が存在する。前者には「C++」「Objective-C」「Java」「C#」「Swift」などが、後者には「Smalltalk」「Eiffel」「Self」などがある。
言語仕様の中でオブジェクト指向の存在感が比較的高い代表的なプログラミング言語を以下に列挙する。
Simula 67 1967年
- 1962年に公開されたSimulaの後継版であり、クラス仕様を導入した最初の言語。現実世界の擬似モデルを観測するシミュレーション・プログラム記述用に開発され、後に汎用化された。C++、Java、C#に代表されるクラス主体OOPのルーツとされる。
Smalltalk 1980年
- オブジェクト間の相互作用を担うメッセージ仕様を導入した最初の言語。オブジェクト指向という言葉は、Smalltalk開発者がその言語仕様を説明する中で生み出された。メッセージ主体OOPの元祖であり、高度に柔軟なオブジェクトの連携動作を表現できた。また、後に仮想マシンと呼ばれる専用のランタイム環境も用意された。
C++ 1983年
C言語にクラス主体OOPに基づく言語仕様を追加したもの。Simulaの影響を強く受けている。クラス仕様によってカプセル化、継承、多態性を表現している。またジェネリクスに相当するテンプレート機能も備えていた。元がC言語であるため、OOPから逸脱したコーディングも多用できる点が物議を醸したが、その是非はプログラマ次第であるという結論に落ち着いた。
Objective-C 1984年
C言語にメッセージ主体OOPに基づく言語仕様を追加したもの。Smalltalkの影響を強く受けている。OOP的には前述のC++よりも正統であると見なされた。他のプログラミング・パラダイムも共存しており、Smalltalkよりもコーディングし易くなった。
Object Pascal 1986年
Pascalにクラス主体OOPに基づく言語仕様を追加したもの。
Eiffel 1986年
Pascalをベースにしてクラス主体OOPに基づく言語仕様とジェネリクスのパラダイムを追加した。型付けは静的に限られ、非参照データを自動解放するガーベジコレクタを持ち、多重継承時の問題を回避する仕組みや例外処理など高い堅牢性を備えた。これらは後のJavaやC#の手本となった。
Self 1987年
- メッセージ主体OOPに分類される。従来のクラスベースに対して、システム側が用意したオブジェクトを複製して任意の拡張を施すプロトタイプベースを実装した。実行時コンパイラ(just-in-time compiler)を備える専用のランタイム環境で実行された。
CLOS 1988年
Common Lispにメッセージ主体OOPに基づく言語仕様を追加したもの。
Python 1990年
- クラス主体OOPに分類される。インタプリタ式で動作する。言語仕様を簡素化し自動メモリ管理機能を実装して扱いやすく理解しやすいOOPを目指している。後のOOPスクリプト言語の手本となった。
Java 1995年
- 堅牢性と安全性を重視したクラス主体OOPである。仮想マシン上の実行、ガーベジコレクタ、例外処理などを採用し、多重継承、ジェネリクス、演算子オーバーロードを破棄したがその埋め合わせの仕様も備えられている。メッセージ主体OOP的設計を表現出来るシリアライゼーションやリフレクションの仕様も導入された。非常に整えられたハイブリッドOOP言語である。
Delphi 1995年
- クラス主体OOPに分類される。Object Pascalを発展させたもので、データベースの操作プログラム開発などを主な用途とした。一時期Javaの対抗馬となった。
Ruby 1995年
- クラス主体OOPに分類されるスクリプト言語である。インタプリタ式で動作する。スクリプトでありながら、クラス、マルチスレッド、例外処理、そしてソフトウェアコンポーネント(モジュール)を扱えるMixinといった利便性の高い機能も備えている。
JavaScript 1996年
ウェブアプリケーション開発を主な目的とするOOPスクリプト言語。主にプロトタイプベースでオブジェクトを扱う事でコーディングを簡便にしている。クラス主体OOPに分類される。ECMAScriptとして標準化されている。ECMAScript 2015ではクラス構文をサポートするようになった。
C# 2000年
Javaを強く意識して開発されたクラス主体OOP言語。.NET Frameworkなどの共通言語基盤上で実行される。Javaと同等または部分的に拡張させた仕様を持ち、こちらもよく整えられたハイブリッドOOP言語として知られる。
Visual Basic.NET 2002年
- 従来のVisual Basicの構文をベースにしつつ、クラス主体OOPに基づいて言語仕様が改変されている。.NET Frameworkなどの共通言語基盤上で実行される。
Ceylon 2011年
Javaを元に開発され、その長所と短所を見直しつつ再設計されたOOP言語。Javaの改造版である。またJavaScriptにもコンバートできる。
Swift 2014年
Objective-Cの後継言語。クラス主体OOPとメッセージ主体OOPの中間に位置する。その他のパラダイムも併せ持っている。
OOP言語の仕組み
オブジェクト指向プログラミング言語は、相互にメッセージを送りあうオブジェクトの集まりとしてプログラムを構成することができる仕組みを持つ。
そのために、少なくともオブジェクトについての3つの仕組みと、オブジェクトの管理についての3つの仕組みが必要となる。
- オブジェクトの仕組み
- オブジェクトに蓄えられる情報、データを表現する仕組み。
- 他のオブジェクトにメッセージを配送する仕組み。
- 受け入れ可能な各種メッセージに対応して、処理する事柄を記述する仕組み(メソッド)。
- オブジェクトを管理する仕組み
- オブジェクト間の関係を整理分類して系統立てる仕組み。
- 必要なオブジェクトを作成・準備する仕組み。
- 不要なオブジェクトを安全に破棄する仕組み。
これらをどのように言語の要素として提供し、どのような機械語コードで実現するかによって様々なオブジェクト指向プログラミング言語のバリエーションが生まれる。以下、オブジェクト指向プログラミング言語が提供する様々な要素が上記の仕組みをどのように実現しているかについて概観する。
オブジェクトの概念と実装
オブジェクト (object) はオブジェクト指向プログラミングの中心となる概念であり、この概念を実際にどう実現するかはオブジェクト指向プログラミング言語により異なる。
以下、概念と実際がどう対応しているかについて説明する。
- 概念的には各々のオブジェクトは、プログラムが表現する情報システムの中で能動的な役割を持った存在を表現している。
- 概念的には メッセージを受け取り、その処理の過程で内部に蓄えたデータを書き換え、必要に応じて他のオブジェクトにメッセージを送るといった動作をしている。
- 概念的には コードとデータが一つになっている。
オブジェクトの実装構造
実際のプログラムでは、全てのオブジェクトが互いに全く異なった存在ではなくオブジェクトは種類に分けることが出来る。
例えば勤怠管理のシステムであれば、氏名や年齢、累積勤務時間などのデータは異なっても社員は皆、出勤し退勤するという処理(振る舞い)は同じだろう。このように複数の異なるオブジェクトが同じ種類のメッセージを受け取り共通の処理をするのが普通である。
このような場合、各オブジェクトがそれぞれメッセージ処理のコード(前述の「振る舞い」に当たる)を独自に備えていては無駄である。そこでオブジェクト指向プログラミング言語がオブジェクトを実現する際には多くの場合、内部的にはオブジェクトを2つの部分に分けている。
同一種類のオブジェクトの間で変わらない共通部分
一つは同一種類のオブジェクトに共有される部分、例えばメッセージ処理のコード(振る舞い)や定数(どのオブジェクトでも異ならないデータ)の類である。
同一種類のオブジェクトの間で変わる個々の部分
もう一つは同一種類のオブジェクトでもそれぞれ異なる部分、典型的には各オブジェクトが保持するデータ群である。
クラスのオブジェクト化
動的型付けを採用するオブジェクト指向言語の多くは、クラスより生成するインスタンスの他にメタクラスという機能を持ちクラス自体をオブジェクトとして扱うことが出来る。このためオブジェクトには、インスタンスオブジェクトとクラスオブジェクトという2種類のオブジェクトが存在する。Java等クラスオブジェクトを持たない言語の文化圏では、インスタンスオブジェクトとオブジェクトを混同して説明される事があるが、Objective-CやPython、Ruby等、インスタンスオブジェクトとクラスオブジェクトが別であるオブジェクト指向言語では区別して説明される。[2][3][4]元々はSmalltalkから始まった用語である。
プライベートデータの扱い方
そしてあるオブジェクトOにメッセージを配送し適切なメッセージ処理コード(振る舞い)を呼び出す際には、まず対象となるオブジェクトOについて共通部分の格納場所を見つけて適切なコードを選び出し、次にそのコードに対して処理対象となるオブジェクトO固有のデータの所在を示すオブジェクトIDを渡すようになっている。
各オブジェクトの固有データを識別するオブジェクトIDを表現する方法も様々で、オブジェクトのIDとしては名前、番号なども用いられることがあるが、オブジェクトの固有データを記憶している主記憶上のアドレスがそのまま用いられることもある。アドレスを直接利用することは非常に実行効率の向上に寄与するが、プログラム間でのオブジェクトの受け渡し、セッション間(プログラムが終了して再度起動された時など)でのオブジェクトの受け渡しにはそのまま利用することができない。
また各オブジェクトの固有データから共通部分の格納場所を見つける方法もまた各言語により異なり、その言語の開発目的に応じて実に多種多様である。
JavaScriptの場合
例えばJavaScriptの場合、各オブジェクトは連想配列であり、名前で表現されたメッセージのIDからメッセージ処理コードである関数への参照を直接見つけ出す。各オブジェクトの固有データもその連想配列に格納されていて、メッセージを処理する関数には連想配列のアドレスが渡される。
Selfの場合
Selfのようなインスタンスベースのオブジェクト指向プログラミング言語では、プロトタイプとなるオブジェクトがメッセージを処理するコードも保持しており、オブジェクトがクローンされて作成されるときにそのプロトタイプのありかを示す情報もコピーされ、メッセージは受け取ったオブジェクトのIDを添えてプロトタイプに送られて処理される(Selfでは実行効率上の問題から後に内部的にクラスを作って利用するようになっている)。
クラスベースの言語の場合
最も普及しているクラスベースの言語では、共通部分はオブジェクトの種類を表現するクラスに保持され、各オブジェクトは固有データと共にそのクラスのIDを保持する。そしてオブジェクトに送られるメッセージはその送り先オブジェクトにあるクラスのIDからクラスを見つけ、その中からメッセージを処理するコードを見つけ出し、処理対象となっているオブジェクトのIDを付してそのコードを呼び出す仕組みになっている。
コンポジション
コンポジションは、複数のオブジェクトがある一つのオブジェクトの構成要素となっている巨大なオブジェクト群をいう。コンポジションのもとにあるオブジェクトは同一の生存期間を持ち、一つの巨大な仮想オブジェクトの構成部品として機能する。
メッセージ
メッセージ (message) はオブジェクト間の通信でやりとりされる情報である。メッセージはメッセージ種別を示すIDとメッセージの種別に応じた追加の情報からなる定まった形式を持つ。追加の情報はそれ自身が何らかのオブジェクトやオブジェクトのIDである場合もある。メッセージの配送には大別して2つの方式がある:
- 同期式
- オブジェクトがメッセージの送信を依頼すると相手が受信、処理して結果を返すまでそのオブジェクトは処理を中断して待つ。
- 非同期式
- オブジェクトがメッセージの送信を依頼した後、相手の応答を待たずにオブジェクトは処理を続行する。処理結果は別のメッセージとして返される。
両者とも一長一短がありどちらがすぐれているとは言えない。また並列・並行処理が可能な環境では一方の仕組みがあれば、それを利用してもう一方も実現可能である。一般的な傾向としては、メッセージの伝送や処理に時間が掛かる場合は非同期式の方が効率は良く、そうでない場合には同期式の方が挙動が分かりやすく利用しやすい。
並列処理・並行処理システムを記述する言語や分散システムを記述する言語ではOSなどが提供するメッセージ機能や自前の配送メカニズムを使って非同期式でメッセージが配送される場合もあるが、一般にオブジェクト指向プログラミング言語ではその多くが同一のプログラム内の通信であるので同期式のメッセージ配送が利用される。特にコンパイルされるタイプのオブジェクト指向プログラミング言語では、しばしば特別なメッセージ配送の仕組みを用意せず、特別な形式の関数の呼び出しでメッセージの配送を直接に表現する。即ち、各メソッドを内部的には関数として実現し、メッセージIDはメソッド名で表し、関数の第一引数としてオブジェクトIDを渡し(この第一引数は多くの言語で特別な記法で表される)、追加の引数としてメッセージの追加部分の情報を渡すのである。こうするとメッセージ送信は直接的なメソッドの関数呼び出しとして表せる。ただし、プログラムで継承の仕組みが利用されている場合はプログラムのテキストからだけでは呼び出すべきメソッドが決定できない場合があるので、実行時にメソッドを決定するためにメソッド・サーチや仮想関数テーブルといった仕組みが必要となる。
多くのプログラミング言語においてメッセージは、メソッド呼び出しの比喩でしかないことが多い。SmalltalkやObjective-Cの様な言語では、メッセージはメソッド呼び出しとは独立した機構として存在している。メッセージが機構として存在する言語では、メッセージをオブジェクトに送信した際、宛先のオブジェクトにメッセージで指定したメソッドが存在しない場合でもメッセージを処理することが出来る。これを利用し、メッセージの配送先を別のオブジェクトに指定したり、メッセージを一時保存したり、不要なメッセージを無視する等といったメッセージ処理が行われる。
クラス
クラスベース
クラス (class) は大多数のオブジェクト指向プログラミング言語で提供されている仕組みであり、上記の機能の殆ど全てに関わりがある。概念的にはクラスはオブジェクトの種類を表す。このためオブジェクトはクラスに属するという言い方をする。あるクラスに属するオブジェクトのことをそのクラスのインスタンス (instance) と呼ぶ。データ型の理論から見た場合クラスは型を定義する手段の一つである。クラスによってオブジェクトを記述する言語をクラスベース (class-based) のオブジェクト指向プログラミング言語と呼ぶ。
ハイブリッド型オブジェクト指向プログラミング言語では在来のレコード型(Cでは構造体)の構文を拡張してクラスの定義を行うようにしたものが多い。
多くのオブジェクト指向プログラミング言語ではクラスをデータメンバとメソッドの集まりとして記述する。平たく言えばデータ・メンバの集まりはオブジェクトが保持するデータの形式を定め、各メソッドはそれぞれオブジェクトが処理する特定のメッセージの処理方法を定める。しばしばデータ・メンバとメソッドには個別にアクセス権が設定できるようになっていて、そのクラスに属するオブジェクトが内部的に利用するものと他のクラスに属するオブジェクトに公開するものを分類できるようになっている。多くの場合、公開されたメソッドの集まりは全体として処理可能なメッセージのカタログの機能、即ちインタフェースを提供する。各言語によって異なるが特定の名前のメソッドを定めて、オブジェクトの生成や初期化時の処理、廃棄時の処理などを記述できるようにすることも多い。
多くの言語でクラスは言語の要素として直接実現されているが、これは実行効率のためであり、そのように実現することが必須というわけではない。実際、各クラスをそれぞれオブジェクトとして提供する言語も存在する(例:Smalltalk)。このような言語ではある種のリフレクション (reflection) が可能となる。即ち必要があればプログラムで実行時にクラスの動作を変更することが可能である。これは非常に大きな柔軟性を提供するが、言語処理系による最適化が難しいため実行効率は低下することが多い。近年[いつ?]では柔軟性と効率性を両立させるために基本的に言語要素としてクラスを提供した上で、リフレクション機能が必要なプログラムに対しては必要に応じて各クラスに対応するクラス・オブジェクトをプログラムが獲得できるようにしている言語が現れてきている。(例:JavaのリフレクションAPI)
プロトタイプベース
クラスは非常に多くのオブジェクト指向プログラミング言語で提供されている機能ではあるが、オブジェクト指向プログラミング言語に必須の機能というわけではない。実際にオブジェクトの管理や、データ・メンバやメソッドの記述、継承に際してクラスという仕組みに依存せずに、もしくはクラスという仕組み自体を持たずに別の手段でこれらを実現している言語も存在する。このような言語をインスタンスベース (instance-based)、オブジェクトベース (object-based) あるいはプロトタイプベース (prototype-based) のオブジェクト指向プログラミング言語と呼ぶ。インスタンスベースまたはそれに類するオブジェクト指向プログラミング言語には以下のようなものがある:
- Self
- JavaScript
- NewtonScript
- ドリトル
- Squeak eToys(Squeakの非開発者向けビジュアルスクリプト言語。SqueakToys とも)
なお、クラスベースの言語とインスタンス・ベースの言語との間には明確な境界線はない。たとえば、インスタンス・ベースの代表格ともいえる Self には、traits と呼ばれるクラスのような仕組みが追加されているし、JavaScript、NewtonScript に至っては traits 類似の仕組みを「クラス」と呼称している。また逆に、クラスベースの言語でもクローンを行うメソッドを備え、委譲の仕組みを記述すればある程度はインスタンス・ベースのスタイルでプログラムを記述できる。
インスタンス・ベースの言語ではオブジェクトの生成は既存のオブジェクト、特にプロトタイプ(prototype、原型)と呼ばれるオブジェクトからのクローンによって行われる。当然、一群のクローンはその親、ひいてはプロトタイプと同一の種類のオブジェクトと見なされる。メソッドはプロトタイプ・オブジェクトに属し、メッセージは委譲によってそのオブジェクトが覚えているコピー元へ向かってプロトタイプまで順にメッセージが中継されてから処理される。新しい種類のオブジェクトが必要な場合は適当なオブジェクトをクローンした後で必要なデータ・メンバやメソッドを追加あるいは削除し新たなプロトタイプとすることで行われる。追加されたのでないメソッドに対応するメッセージについてはコピー元のオブジェクトに処理を委譲する。
クラスベースの言語との関係について考えてみると、クローンはプロトタイプと同一の「クラス」に属すると見なし、データ・メンバやメソッドが追加・削除されてあらたなプロトタイプが作られると別の「クラス」が内部的に生成されると考えることができる。ここでデータ・メンバやメソッドの追加のみを許して削除を許さないよう制限すればクローンの「クラス」がその親の「クラス」を継承した場合と同等になる。このためメッセージが委譲の連鎖をたどって配送されるという効率上の問題を無視すれば理論上、インスタンス・ベースの言語の記述能力はクラス・ベースの言語を包含していると言える。ただ、インスタンス・ベースの言語でも実行効率上の問題からなんらかのクラスに似た仕組みを備えている場合が多い。
データメンバ
データメンバ (data member) は、他のオブジェクトに対する参照やポインタであるか、他のオブジェクトそのものである。参照かポインタである場合にはそのデータメンバの参照するのはデータメンバが記述されているクラスそのもののインスタンスに対する参照であっても良い。
一般にデータメンバはインスタンスデータメンバ(インスタンスフィールド)とクラスデータメンバ(静的変数)の2種類に大別できる。効率上の観点から言語が提供する基本オブジェクトの定数を表すデータメンバは特別扱いされる。そのような定数を表すデータメンバを特に定数データメンバ (constant data member) と呼ぶ。データメンバはC++などの言語ではメンバ変数 (member variable)、Javaなどではフィールドと呼ばれることがあり、UMLでは属性と呼ばれる。
インスタンスデータメンバ
インスタンスデータメンバ(一般に単にデータメンバと言われる場合はこちら)はそのクラスのインスタンス各々に保持される。インスタンスデータメンバの集まりはそのクラスのインスタンスが保持するデータの形式を定める。インスタンスデータメンバは単にデータメンバと呼ばれることも多い。
Smalltalkではインスタンス変数 (instance variable) と呼ばれる。
クラスデータメンバ
クラスデータメンバはそのクラスオブジェクトとインスタンスオブジェクトの間で共有されるデータである。
Smalltalkではクラスデータメンバはクラス変数 (class variable) と呼ばれる。また、C++・Javaでは歴史的事情によりクラスデータメンバは静的データメンバ (static data member)、静的変数 (static variable)、静的フィールド (static field) と呼ばれる。
ただし、Smalltalkのクラス変数はC++やJavaのクラス変数とは異なる。Smalltalkにおいて、C++やJavaのクラス変数と同等となる変数はプール辞書 (pool dictionary) と呼ばれる。
メソッド
メソッド (method) は特定の種類のメッセージの処理方法を記述したものである。メソッドもインスタンス・メソッドとクラス・メソッドの2種にできる。インスタンス・メソッドはそのクラスの各インスタンスオブジェクトを操作し、クラス・メソッドはクラスオブジェクトを操作する。メソッドとの集まりはそのクラスのオブジェクトが処理可能なメッセージのカタログの機能を果たす。
一例として、C++では、メソッドはメンバ関数 (member function) や関数メンバ (function member) と呼ばれる。これはC++がグローバル関数との区別をつけることと、クラスを抽象データ型の拡張と位置づけ、非メッセージメタファな言語思想を持っている為である。これら言語ではメソッドをオブジェクト(=クラスやインスタンス)の持ち物として捉えず、クラスに定義された機能要素であると考える。メッセージメタファを否定するため、同時にメッセージを実行するメソッド(手法)ではありえない。
クラスメソッド
クラス・メソッドだが、オブジェクト指向の本義に立ち返れば、クラス・メソッドがあるということはクラスがメッセージをレシーブできるという事になる。
クラスがメソッドを持つことは便利だが、クラスをオブジェクトとすると実行効率に劣るため、双方の利点を享受できるこのような折中的仕様を取る言語は多い。
C++ではクラスはオブジェクトでは無いが、一方でクラスに属するメソッドは存在する。
Eiffelではクラスはオブジェクトでは無いためクラスのメソッドであるクラス・メソッドは存在しない。
Smalltalkではクラスもオブジェクトの一種であるため当然クラスはメソッドをもつ。
各言語におけるクラスメソッドの呼称
クラス・メソッドは、C++では静的メンバ関数 (static member function) と呼ばれる。これはクラスがオブジェクトでない言語にとってはクラス・メソッドより正確な表現であり適切である。("static" とはCのstatic変数に由来しauto変数の対語である。関数コールによりスタック上に生成される関数インスタンスに依存しない変数と、インスタンス生成有無にかかわらず実行できる関数の類似による。)
Javaではクラス・メソッドは静的メソッド (static method) とも呼ばれることもある。
システムメソッド
言語によっては特定の名前のインスタンス・メソッドやクラス・メソッドにオブジェクトの生成、初期化、複製、廃棄といった機能を固定的に割り当てている。
コンストラクタとデストラクタ
初期化に利用されるメソッドをコンストラクタあるいは構築子 (constructor)、廃棄時に利用されるメソッドをデストラクタあるいは消滅子[要出典] (destructor) と呼んで特別に扱うことが多い。
コンストラクタが初期化だけを担う場合はイニシャライザあるいは初期化子 (initializer) と呼ばれることもある。
Javaはオブジェクトの寿命管理にガベージコレクションを用いるため、デストラクタをサポートしない。ただし、オブジェクトがガベージコレクションによって破棄されるときに呼び出されるファイナライザ (finalizer) をサポートし、Object#finalize()
メソッドがその役割を果たす。ただし、ファイナライザはC++のデストラクタと違ってユーザーコードで明示的に呼び出すことはできない。ファイナライザが呼び出されるタイミングをプログラマが制御することはできず、最終防壁(フェイルセーフ)としての役割しか持たないため、Javaにおけるファイナライザは本当に必要でない限り使用するべきではない。C#もファイナライザをサポートする(構文はC++のデストラクタに似ており、かつてはデストラクタと呼ばれていたが、役割はJavaのファイナライザと同じである)。
データ型の理論においては保持されるデータが必ずその型で認められる正しい値の範囲に収まることを保証するため、生成されるオブジェクトのデータ・メンバが必ず適切なコンストラクタによって初期化されるように求める。またオブジェクトが入出力機器やファイルや通信、プロセスやスレッド、ウィンドウとウィジェットなどハードウェアやオペレーティングシステム (OS) が提供する資源を管理するために利用される場合に、コンストラクタやデストラクタでそれらの資源の使用開始(オープン処理)や使用終了(クローズ処理)をそれぞれ管理し、通常のメソッドでそれらにまつわる各種サービスを提供するようにすることで、それらのリソースがあたかもプログラム中のオブジェクトであるかのように自然に取り扱うことができるようになる(RAII)。
C++やJavaなどでは、コンストラクタはクラスと同じ名前を持ち、戻り値を持たないメソッドとして定義される。C++では一部のコンストラクタは型変換演算子として、また暗黙の型変換にも利用される。
ガベージコレクション
オブジェクト指向プログラミング言語では、オブジェクトへの参照やポインタが多用される。そのため、オブジェクトのメモリへの割り当てと破棄に関して、ガベージコレクションによる自動管理機能を備えているものが多い。ただし、すべての言語が備えているわけではない。例えば、C++はガベージコレクションを備えていない。
アクセスコントロール
オブジェクト指向プログラミングにおいて、オブジェクトは、カプセル化されておりブラックボックスである。したがって、処理するメッセージのカタログ、つまりインタフェースだけが利用者に公開され、内部の詳細は隠されるのが基本である。しかし、あるクラスのインスタンスの内部だけで利用されるメソッドまで公開してしまうと、利用者にとって煩雑である。また、定数データ・メンバのようなものは一々メソッドでアクセスするようにせず公開してしまっても、カプセル化の利点は失われず効率的でもある。そこで、オブジェクトを定義するプログラマが各データ・メンバやメソッドについて公開・非公開を設定できる機能を用意している言語は多い。
例えば、Javaでは、データ・メンバやメソッドの宣言にpublicと指定すれば、他オブジェクトから自由に利用でき(公開と呼ばれる)、privateと指定すればオブジェクト内だけで利用できるようになる(非公開と呼ばれる)。
しかし、ある機能を提供するのに、一個ではなく一群のクラスに属するオブジェクトでそれを記述するのが相応しい事例がある。そのような場合、関係する一群のオブジェクト間でだけデータ・メンバやメソッドを利用できれば便利である。それを可能にするための拡張がいくつか存在する。例えば、継承を利用しているときに、あるクラスが子孫にだけ利用を許可したいデータ・メンバやメソッドがある場合、Javaではprotectedを指定することでそれを実現できる(限定公開と呼ばれる)。また、ある一群の機能を実現するクラスのライブラリで、その実現に関連するクラスに属するオブジェクトだけがデータ・メンバやメソッド利用できるようにしたい場合も考えられる。また、Javaでは、ライブラリを構成するクラス群を表現するパッケージ (package) という仕組みがあり、特に指定がない場合は同一パッケージに属するクラスのオブジェクト間でのみデータ・メンバやメソッドを相互に利用可能である。その他にも、デザインパターンの一つであるFacade パターンでは、この仕組みがテクニックとして応用されている。また、C++ではフレンド宣言という仕組みがあり、あるクラスで外部非公開に指定されているデータ・メンバやメソッドについて、その利用を許可するクラスや関数のリストをクラス内に列挙することができる。
なお、public、private、protectedというキーワードは、多くのプログラミング言語で用いられているが、その示す意味は言語ごとに差異があるため、注意が必要である。
脚注
^ コンピュータ・プログラミングのパラダイムについては『新しいプログラミング・パラダイム』などを参照: http://www.kyoritsu-pub.co.jp/bookdetail/9784320024939
^ Objective-Cプログラミ
ング言語[1]
^ Classes ― Python v2.7.3 documentation[2]
^ クラス/メソッドの定義 (Ruby manual) [3]
関連項目
- メッセージ (コンピュータ)
- メソッド (計算機科学)
- フィールド (計算機科学)
- インスタンス変数
- クラス変数
- クラス (コンピュータ)
- インスタンス
- カプセル化
- 継承
- 委譲
- プログラミング言語
- オブジェクト指向モデリング
- オブジェクト指向分析設計
- オブジェクト指向
- オブジェクトデータベース
- トップダウン設計とボトムアップ設計
- オブジェクト関係マッピング
|