コンテンツにスキップ

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

出典: フリー教科書『ウィキブックス(Wikibooks)』

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

[編集]

オブジェクト指向プログラミング(OOP; Object Oriented Programming)は、主要なプログラミングパラダイムの1つです。 オブジェクトは、モデル化されるべき実際のエンティティをエミュレートするためのメタファーとして使用されます。

オブジェクト指向プログラミングは、継承(inheritance)、凝集度(cohesion)、抽象化(abstraction)、ポリモーフィズム(polymorphism)、結合(coupling)、カプセル化(encapsulation)といった1960年代の技術をベースにしています。

オブジェクト指向プログラミングは、1990年代初頭に流行しはじめ、現在、オブジェクト指向をサポートするプログラミング言語は多岐にわたっています。

オブジェクト指向プログラミング言語を使ったプログラミング = オブジェクト指向プログラミング?

[編集]

オブジェクト指向プログラミング言語は、オブジェクト指向プログラミングを構文やライブラリで支援することで、より容易により木目細かなエラーチェックを行うことを実現したプログラミング言語です。

オブジェクト指向プログラミング言語を使っても、オブジェクト指向プログラミングとは言い難いプログラミングスタイル(例:プロシージャを整頓するためだけにクラスを使い、機能が互いのに関係がないようなプログラミング)は可能です。

また、非オブジェクト指向プログラミング言語(例:C言語)を使ったとしても、カプセル化・継承・ポリモーフィズムなどのオブジェクト指向プログラミングの特徴を持ったプログラミングは可能です(例:X Toolkit Intrinsicsのウィジェットを継承し、新たなルックアンドフィールを持ったX Athena WidgetsMotif Widgetsは実装されています)。

特徴

[編集]

オブジェクト指向プログラミングでは、オブジェクトを使用しますが、OOP対応を謳っている言語でも、関連する技術や構造のすべてが直接サポートされているわけではなく、(メソッドやメッセージではなく)被演算子と演算子という数式アナロジーが使われます。 以下の機能は、クラス指向やオブジェクト指向が強いとされる言語(またはOOP対応のマルチパラダイム)に共通するものであるが、特筆すべき例外もあります。

関連する技術や構造

[編集]

継承(inheritance)、凝集度(cohesion)、抽象化(abstraction)、ポリモーフィズム(polymorphism)、結合(coupling)、カプセル化(encapsulation)

クラスベースとプロトタイプベース

[編集]

オブジェクト指向プログラミング言語は、クラスベースとプロトタイプベースに大別されます(どちらにも分類し難い言語もあります)。 OOPをサポートする言語では、コードの再利用と拡張性のために、クラスまたはプロトタイプの形で継承を使用するのが一般的です。クラスを使用する言語は、2つの主要なコンセプトをサポートしています。

クラスベース
型→ユーザー定義の型の概念の拡張である「クラス」を特徴にします。
クラスとインスタンスは、型と変数の指し示している領域に対応します。
プロトタイプベース
クラスベースのクラスに相当するものはなく、既存のオブジェクトを雛形に新たなオブジェクトを生成し、新たなオブジェクトは雛形となったオブジェクトからフィールドとメソッドを引き継ぎます。

一部の言語では、クラスとオブジェクトは、traitやMix-inなどの他の概念を用いて構成することができます。

クラスベースのオブジェクト指向

[編集]

クラスベースの言語では、あらかじめクラスが定義されており、オブジェクトはクラスに基づいてインスタンス化されます。

オブジェクトとクラス
[編集]
クラス
与えられたタイプまたはクラスのオブジェクトのデータフォーマットと利用可能な手続きの定義。データと手続き(クラスメソッドとして知られています)自体を含むこともあり、つまりクラスはフィールド(データメンバー・属性とも)とメソッド(メンバー関数とも)を含む。
オブジェクト
クラスのインスタンス
オブジェクトは、現実の世界にあるものに対応することがあります。例えば、グラフィックプログラムでは、"circle"、"square"、"menu "などのオブジェクトがあります。また、オンライン・ショッピング・システムでは、「ショッピング・カート」、「カスタマー」、「プロダクト」などのオブジェクトがあります。オブジェクトは、オープン・ファイルを表すオブジェクトや、ヤードポンド法からメートル法に変換するサービスを提供するオブジェクトなど、より抽象的な実体を表すこともあります。

オブジェクト指向プログラミングとは、単なるクラスやオブジェクトではなく、データフィールドやメソッドを含むオブジェクト(データ構造)を中心としたプログラミングパラダイム全体のことです。これを理解することは不可欠です。 クラスを使って、関係のない手続きをまとめて整理することは、オブジェクト指向ではありません。

各オブジェクトは、特定のクラスのインスタンスであると言われています(例えば、nameフィールドに "Mary"が設定されているオブジェクトは、Employeeクラスのインスタンスであると言えます)。オブジェクト指向プログラミングにおけるプロシージャはメソッドと呼ばれ、変数はフィールド、メンバー、アトリビュート、またはプロパティとも呼ばれます。そのため、以下のような用語があります。

クラス変数
クラス全体に属するもので、1つのコピーしか存在しません。
インスタンス変数または属性
個々のオブジェクトに属するデータで、各オブジェクトはそれぞれのコピーを持っています。
メンバー変数
特定のクラスで定義されているクラス変数とインスタンス変数の両方を指します。
クラスメソッド
クラス全体に属し、クラス変数とプロシージャコールからの入力にのみアクセスできます。
インスタンス・メソッド
個々のオブジェクトに属し、呼び出された特定のオブジェクトのインスタンス変数、入力、およびクラス変数にアクセスできます。

オブジェクトは、複雑な内部構造を持つ変数のようにアクセスされ、多くの言語では効果的にポインタとなり、ヒープやスタック内のメモリにある当該オブジェクトの単一インスタンスへの実際の参照として機能します。オブジェクトは、内部コードと外部コードを分離するために使用できる抽象化の層を提供します。外部のコードは、特定の入力パラメータセットで特定のインスタンスメソッドを呼び出したり、インスタンス変数を読み込んだり、インスタンス変数に書き込んだりすることで、オブジェクトを使用することができます。オブジェクトは、コンストラクタと呼ばれるクラス内の特殊なメソッドを呼び出すことで生成されます。プログラムは、実行中に同じクラスのインスタンスを多数作成し、それらが独立して動作することがあります。これは、同じ手順を異なるデータセットに対して使用するための簡単な方法です。

クラスを用いたオブジェクト指向プログラミングをクラスベース・プログラミングと呼ぶことがあるが、プロトタイプベース・プログラミングではクラスを用いないのが一般的です。そのため、オブジェクトとインスタンスの概念を定義するために、大きく異なるが類似した用語が使用されています。

プロトタイプベースのオブジェクト指向

[編集]

プロトタイプベースの言語では、オブジェクトが主な実体です。クラスも存在しません。オブジェクトのプロトタイプは、そのオブジェクトがリンクされている別のオブジェクトに過ぎません。すべてのオブジェクトは、1つのプロトタイプリンクを持っています(1つだけです)。新しいオブジェクトは、プロトタイプとして選ばれた既存のオブジェクトに基づいて作成することができます。

プロトタイプベースの言語では、本質的にインスタンス化と継承は同じメカニズムの別の角度から見た射影です。

多くのオブジェクト指向プログラミング言語はクラスベースですが、殆どのウェブブラウザはJavaScriptエンジンを搭載していることから、プロトタイプベースのオブジェクト指向言語が使われ機会は多く、WikibooksのインフラストラクチャーであるMediaWikiのモジュールの記述言語のLuaもプロトタイプベースのオブジェクト指向言語です。

動的ディスパッチ/メッセージパッシング

[編集]

メソッドの呼出しに応じて実行する手続きコードを選択するのは、外部のコードではなく、オブジェクト管理機構の責任で、通常はオブジェクトに関連付けられたテーブルから実行時にメソッドを検索します(仮想関数テーブル)。 この機能は動的ディスパッチとして知られており、オブジェクトと、すべてのインスタンスに対して操作の実装が固定された(静的な)抽象データ型(またはモジュール)とを区別しています。 呼出しの可変性が、呼出されたオブジェクトの単一の型以上に依存している場合(つまり、少なくとも1つの他のパラメータオブジェクトがメソッドの選択に関与している場合)、多重ディスパッチと言います。

メソッドの呼出しは、メッセージパッシングとも呼ばれます。これは、メッセージ(メソッドの名前とその入力パラメータ)がディスパッチのためにオブジェクトに渡されるものとして概念化されています。

カプセル化

[編集]

カプセル化は、オブジェクト指向プログラミングの概念の一つで、データとそのデータを操作する関数を結びつけ、外部からの干渉や誤用から両者を保護することです。データのカプセル化は、OOPの重要な概念である「データの隠蔽」につながっています。

クラスが呼び出しコードにオブジェクトの内部データへのアクセスを許可せず、メソッドを通してのみアクセスを許可している場合、これはカプセル化として知られる強力な抽象化または情報隠蔽の形態です。 一部の言語(C++やJavaなど)では、クラスがアクセス制限を明示的に行うことができます。 例えば、内部データをprivateキーワードで表し、クラス外のコードが使用することを意図したメソッドをpublicキーワードで指定します。 メソッドにはpublic、privateのほか、protected(同じクラスとそのサブクラスからのアクセスは許可するが、異なるクラスのオブジェクトからのアクセスは許可しません)のような中間レベルのデザインもあります。 他の言語(Pythonなど)では、カプセル化は慣例によってのみ強制されます(例えば、プライベートメソッドの名前はアンダースコアで始まることがあります)。 カプセル化することで、外部のコードがオブジェクトの内部動作に関与することを防ぐことができます。 これにより、コードのリファクタリングが容易になります。 例えば、クラスの作者は、外部のコードを変更することなく、そのクラスのオブジェクトが内部でどのようにデータを表現しているかを変更することができます(publicメソッドの呼び出しが同じように動作する限りにおいて)。 また、特定のデータに関連するすべてのコードを同じクラスにまとめることで、他のプログラマーが理解しやすいように整理することができます。 カプセル化は、結合度の低減(decoupling)を促進する技術です。

包含、継承、および委譲

[編集]

包含

[編集]

オブジェクトは、そのインスタンス変数に他のオブジェクトを含めることができますが、これを包含(オブジェクト・コンポジション; object composition )といいます。 たとえば、Employee クラスのオブジェクトは、"first_name" や "position" といった自身のインスタンス変数に加えて、Address クラスのオブジェクトを (直接またはポインタを介して) 含むことができます。

継承

[編集]

オブジェクト指向プログラミングにおいて、あるオブジェクトやクラスを別のクラス(クラスベース継承)やオブジェクト(プロトタイプベース継承)をベースにして、同様の実装を保持する仕組みのことを「継承」といいます。

クラスベース継承

[編集]

クラスベース継承では、スーパークラスと呼ばっる既存のクラスから新しいクラス(サブクラス)を派生させ、クラスの階層を形成します。継承によって生成されたオブジェクト(派生オブジェクト)は、スーパークラス(あるいはプロトタイプ)のコンストラクタ、デストラクタ、オーバーロード演算子、フレンド関数を除き、親オブジェクトのすべてのプロパティと動作を獲得します。 サブクラスは、スーパークラスで定義されたメソッドをオーバーライドもできます。 継承によって、プログラマは既存のクラスの上にクラスを作成し、同じ振る舞いを維持しながら新しい実装を指定し(インターフェースの実現)、コードを再利用し、パブリッククラスやインターフェースによって元のソフトウェアを独自に拡張することができます。継承によるクラスの関係は、有向無サイクルグラフを生み出します。

スーパークラス
[編集]

スーパークラスは、クラスベース継承における継承元となるクラス。

サブクラス
[編集]

サブクラスは、クラスベース継承における継承先となるクラス。

メタクラス
[編集]

クラスベース継承において、(インスタンスとしての)クラスもまたオブジェクトとして扱おうとした時、クラスの属するクラス(=メタクラス)が仮定されます。 では、メタクラスの属するクラスは?・・・メタクラス自身もメタクラスのインスタンスです。 ここで「有向無サイクルグラフ」の仮定が壊れます。 メタクラスの位置づけは言語によって様々で、メタクラスを持たないオブジェクト指向言語もあります。 初期のSmalltalk(Smalltalk-76)では、Classという単一のクラスがメタクラスでしたが、Smalltalk(Smalltalk-80)では、すべてのクラスに独自のメタクラスがあり、それぞれのメタクラスは単一のMetaclass classのインスタンスです(メタのメタのメタ…の無限の連鎖は避けている)。少し想像すると判るように、メタクラスの操作は概念上のシフトが必要で、微妙で煩雑なものとなりがちです(パラドクスと言ってもいい)。 このような複雑さを解決するために、「クラスなんてなかった、インスタンスはインスタンスの複製によって生成して、複製を新たに修飾し目的を達成する」というアプローチが生まれました。これが、プロトタイプベースのオブジェクト指向/プロトタイプベース継承です(プロトタイプベースはインスタンスベースとも呼ばれます)。

プロトタイプベース継承

[編集]

プロトタイプベース継承では、プロトタイプと呼ばれる既存のオブジェクトを複製し新しいオブジェクトを派生させ、プロトタイプの階層を形成します。複製されたオブジェクト(派生オブジェクト)は、プロトタイプへの参照を持ち、それを通じてプロトタイプのすべてのプロパティ(データプロパティと関数プロパティ)を獲得します。 派生オブジェクトは、プロトタイプで定義されたプロパティをオーバーライドもできます。 継承によって、プログラマは既存のオブジェクトから新しいオブジェクトを生成し、同じ振る舞いを維持しながら新しい実装を指定し(インターフェースの実現)、コードを再利用し、パブリックオブジェクトやインターフェースによって元のソフトウェアを独自に拡張することができます。継承によるオブジェクトの関係は、有向無サイクルグラフを生み出します。

プロトタイプ
[編集]

プロトタイプは、プロトタイプベース継承における継承元となるオブジェクト。

派生オブジェクト
[編集]

派生オブジェクトは、プロトタイプベース継承における継承先となるオブジェクト。 派生オブジェクトも、プロトタイプになることができるので、ことさら派生を強調せず、単にオブジェクトと称されます。

プロトタイプ チェーン
[編集]

すべてのオブジェクトは、プロトタイプへの参照を保持しており、プロトタイプもまたオブジェクトなので、プロトタイプは単方向リストになります。プロトタイプを手繰ることによる単方向リストを、プロトタイプ チェーン( prototype chain )と呼びます。プロトタイプ チェインを遡ると、自分自身をプロトタイプにする特別なオブジェクトに行き着きます。このオブジェクトが始祖のオブジェクトで、JavaScriptでは Object、Lua では Table です。 オブジェクトのプロパティを参照する時、まずオブジェクト自身のスロットにプロパティ キーが無いかしら調べ、あればプロパティ 値を返します。なかった時は、1つプロトタイプ チェインを遡り、探しみつかればその値を返します。始祖のオブジェクトまで遡っても見つからなければ「存在しないプロパティの参照」とします。 プロパティのオーバーライドの実装は、このようにプロトタイプ チェーンによって実現しています。

多重継承

[編集]

一部の言語では多重継承が可能ですが、オーバーライドの解決が複雑になる可能性があります。 多重継承が可能な言語では、Mix-inは単なるクラスであり、is-a-type-ofの関係を表していません。

Mix-in

[編集]

Mix-inは通常、複数のクラスに同じメソッドセットを追加するために使用されます。 例えば、UnicodeConversionMixinクラスは、共通の親を持たないFileReaderクラスとWebPageScraperクラスにインクルードすることで、unicode_to_ascii()というメソッドを提供することができます。

抽象クラス

[編集]

抽象クラスは、インスタンス化してオブジェクトにすることはできません。 抽象クラスは、インスタンス化できる他の「具象」クラスに継承するためにのみ存在します。

Java: final class

[編集]

Javaでは、クラスがサブクラス化されるのを防ぐためにfinalキーワードを使用することができます。

final class MyFinalClass {
    . . .
}

開放/閉鎖原則

[編集]

W:開放/閉鎖原則」とは、クラスや関数は「拡張に対してはオープンであるが、変更に対してはクローズであるべき」と提唱するものです。

委譲

[編集]

委譲( Delegation )もまた、継承の代わりに使用できる言語機能の一つです

継承よりも合成

[編集]

継承よりも合成を重視する考え方( Composition over inheritance )は、継承の代わりに合成を用いてhas-a関係を実装することを提唱しています。 例えば、Employee クラスは、Person クラスを継承する代わりに、Employee オブジェクトに内部の Person オブジェクトを(プライベートな)メンバー変数として持つ戦略で、Person クラスが多くの公開属性やメソッドを持っていても、外部のコードから隠すことができます。

Go: 継承を構文としてはサポートしない

[編集]

Goは、継承を全くサポートしません。ただしGoは、構造体のメンバーのメンバーをメンバー名を間に狭まずに参照することができるので、実質的に包含と継承の区別がありません。この仕組は、「名前が衝突したらどうするんだ!」と批判されることがありますが、通常の継承でもメンバー名の衝突は解決しなければいけないので、問題が増えるわけではありません。

[TODO:Goのコードで説明]

ポリモーフィズム

[編集]

ポリモーフィズムの一種であるサブタイピングとは、呼び出したコードが、サポートされている階層のどのクラスを操作しているのか、スーパークラスなのか、サブクラスなのかを問わないことです。 一方で、継承階層内のオブジェクト間では、同じ操作名でも動作が異なる場合があります。

例えば、Circle型とSquare型のオブジェクトは、Shapeという共通のクラスから派生しています。 Shapeの各型のDraw関数は、自身の描画に必要な機能を実装していますが、呼び出したコードは、描画されるShapeの特定の型には無関心でいられます。

これもまた、クラス階層の外部にあるコードを単純化し、強い関心事の分離を可能にする抽象化の一種です。

脚註

[編集]