「オブジェクト指向プログラミング」の版間の差分
→特徴: 節「オブジェクト指向の三大要素」を解体、 説明を変更・移動 「三大要素」「3大特徴」と広く認められているわけではないと思う。 |
→SOLID: 依存性逆転原則、インターフェース分離原則 について {{疑問点、{{要検証 数か所。 「RnTknn」氏にはユーザー名の変更をお勧めします (WP:U#不適切な利用者名) |
||
83行目: | 83行目: | ||
=== [[SOLID]] === |
=== [[SOLID]] === |
||
2004年以降に広まった[[SOLID]]の[[依存性逆転の原則|依存性逆転原則]]は、OOPの[[パラダイムシフト]]的な潮流になっている。[[リスコフの置換原則|リスコフ置換原則]]による[[継承 (プログラミング)|継承]]の差分プログラミング否定と[[サブタイプ|サブタイピング]]重視を皮切りにし、[[開放閉鎖原則]]によって[[クラス (コンピュータ)|クラス]]の[[サブタイプ|サブタイピング]]は[[インタフェース (抽象型)|インターフェース]]の実装へと進んだが、今度は[[インタフェース (抽象型)|インターフェース]]が具象メソッドも所持可能にされて事実上の[[トレイト]]化へと進んでいる。[[トレイト]]と融合された[[インタフェース (抽象型)|インターフェース]]は、[[依存性の注入]](DI)におけるDependency表現の[[抽象型]]として使われるようになった。[[インターフェース分離原則]]によって機能分化された個々のインターフェースを、それぞれ専用の[[オブジェクト (プログラミング)|オブジェクト]]に注入してその特定の機能と状態を受け持たせて、それらオブジェクトの専門的動作を注入インターフェースに依存させることが、抽象への依存=[[依存性逆転の原則|依存性の逆転]]になる。専門的な機能と状態が依存側に分離されたオブジェクトは、[[Plain Old Java Object|PlainOld]](ごく普通)と言われる。オブジェクトの方は複数のインターフェースを注入指名できることから、ここでオブジェクトの[[イミュータブル]]性も取り沙汰されるようになった。[[Plain Old Java Object|PlainOld]]オブジェクトとDependencyインターフェースのコンテナは、従来の分散オブジェクト([[EJB]]など)コンテナに代わる[[分散システム]]・アプローチになっている。 |
2004年以降に広まった[[SOLID]]の[[依存性逆転の原則|依存性逆転原則]]は、OOPの[[パラダイムシフト]]的な潮流になっている。[[リスコフの置換原則|リスコフ置換原則]]による[[継承 (プログラミング)|継承]]の差分プログラミング否定と[[サブタイプ|サブタイピング]]重視を皮切りにし、[[開放閉鎖原則]]によって[[クラス (コンピュータ)|クラス]]の[[サブタイプ|サブタイピング]]は[[インタフェース (抽象型)|インターフェース]]の実装へと進んだが、今度は[[インタフェース (抽象型)|インターフェース]]が具象メソッドも所持可能にされて事実上の[[トレイト]]化へと進んでいる。[[トレイト]]と融合された[[インタフェース (抽象型)|インターフェース]]は、[[依存性の注入]](DI)におけるDependency表現の[[抽象型]]として使われるようになった。[[インターフェース分離原則]]によって機能分化された個々のインターフェースを、それぞれ{{疑問点範囲|専用の[[オブジェクト (プログラミング)|オブジェクト]]に注入して|date=2021年10月}}その特定の機能と状態を受け持たせて、それらオブジェクトの専門的動作を注入インターフェースに依存させることが、抽象への依存=[[依存性逆転の原則|依存性の逆転]]になる{{疑問点|date=2021年10月|title=依存性逆転原則は 依存性の注入(DI)の形に限定されないはず}}。専門的な機能と状態が依存側に分離されたオブジェクトは、[[Plain Old Java Object|PlainOld]](ごく普通)と言われる。オブジェクトの方は複数のインターフェースを注入指名できることから、ここでオブジェクトの[[イミュータブル]]性も取り沙汰されるようになった。[[Plain Old Java Object|PlainOld]]オブジェクトとDependencyインターフェースのコンテナは、従来の分散オブジェクト([[EJB]]など)コンテナに代わる[[分散システム]]・アプローチになっている。 |
||
[[依存性逆転の原則|依存性逆転原則]]によるこれらの潮流はいわゆる[[関数型プログラミング|関数型]]の導入にも影響している。2000年代のOOP言語で採用されるようになった[[ラムダ式]]や[[第一級関数]]や[[デリゲート (プログラミング)|デリゲート]]は、上述のDependencyインターフェースのミクロ化である。[[イミュータブル]]書式は、PlainOldオブジェクトの作法に倣っている。 |
[[依存性逆転の原則|依存性逆転原則]]によるこれらの潮流はいわゆる[[関数型プログラミング|関数型]]の導入にも影響している。2000年代のOOP言語で採用されるようになった[[ラムダ式]]や[[第一級関数]]や[[デリゲート (プログラミング)|デリゲート]]は、上述のDependencyインターフェースのミクロ化である。[[イミュータブル]]書式は、PlainOldオブジェクトの作法に倣っている。 |
||
[[依存性逆転の原則|依存性逆転原則]]が訴える「抽象への依存」とは、メソッドを通してデータを操作するというデータ抽象と、基底メソッドを派生メソッドで[[オーバーライド]]するというメソッド抽象へのアンチテーゼであり、前者ではメソッドが具象データに、後者ではメソッドが詳細メソッドに依存していることへの反駁であった。依存性逆転原則下のメソッドは一切の専門的動作を外されて、上述の[[インタフェース (抽象型)|インターフェース]]の注入指名と、注入インターフェースとのデータ送受に特化される。[[サーバー]]や[[データベース]]からの抽象体であるインターフェースの注入後に始めて専門的動作が可能になるので、これが抽象への依存になる。インターフェースはその動作専用の状態も保持できるので、それらを複数注入される[[オブジェクト (プログラミング)|オブジェクト]]は[[イミュータブル]]な方が堅牢とされた。PlainOldにDependencyが注入される作法は、オブジェクトに機能オブジェクトがメッセージングされる作法に似ており、[[依存性の注入]]はSmalltalk処理系由来の[[トレイト]]の遅延結合をなぞっているので、OOPの思想は結局のところ[[Smalltalk]]に行き着くようである。 |
[[依存性逆転の原則|依存性逆転原則]]が訴える「抽象への依存」とは、メソッドを通してデータを操作するというデータ抽象と、基底メソッドを派生メソッドで[[オーバーライド]]するというメソッド抽象へのアンチテーゼであり、前者ではメソッドが具象データに、後者ではメソッドが詳細メソッドに依存していることへの反駁であった{{要検証|date=2021年10月}}。依存性逆転原則{{要検証|date=2021年10月|title=依存性の注入(DI)と混同しているのでは。}}下のメソッドは一切の専門的動作を外されて、上述の[[インタフェース (抽象型)|インターフェース]]の注入指名と、注入インターフェースとのデータ送受に特化される。[[サーバー]]や[[データベース]]からの抽象体であるインターフェースの注入後に始めて専門的動作が可能になるので、これが抽象への依存になる。インターフェースはその動作専用の状態も保持できるので、それらを複数注入される[[オブジェクト (プログラミング)|オブジェクト]]は[[イミュータブル]]な方が堅牢とされた。PlainOldにDependencyが注入される作法は、オブジェクトに機能オブジェクトがメッセージングされる作法に似ており、[[依存性の注入]]はSmalltalk処理系由来の[[トレイト]]の遅延結合をなぞっているので、OOPの思想は結局のところ[[Smalltalk]]に行き着くようである。 |
||
== 歴史 == |
== 歴史 == |
2021年10月16日 (土) 06:11時点における版
プログラミング・パラダイム |
---|
オブジェクト指向プログラミング(オブジェクトしこうプログラミング、英: object-oriented programming、略語:OOP)とは「オブジェクト」という概念に基づいたプログラミングパラダイムの一つである。 オブジェクトは、任意個数のフィールド (属性、プロパティまたは変数)で構成されるデータと、任意個数の(メソッドまたは関数)で構成されるコードのひとまとまりで構成される。
オブジェクトの特徴として、オブジェクト自身の手続きが自身のデータフィールドを読み書きできることが挙げられる(オブジェクトにはthis
やself
という概念がある)。また、OOPでは、相互に作用するオブジェクトを組み合わせてプログラムを設計する[1][2]。OOP言語のありかたは多様であるが、最も一般的といえるものは、オブジェクトがクラスのインスタンスであり、また、オブジェクトの型もクラスとして規定されるクラスベースといわれるものである。
オブジェクト指向という言葉自体は、計算機科学者アラン・ケイによって作り出されている。Simula言語などにインスパイアされたケイが1967年頃に口にしたと伝えられるこの造語は[3]、彼が1972年から開発を始めた言語Smalltalkの設計を説明する過程で明確な用語として発信され、1981年頃から知名度を得るようになった。80年代半ばになるとオブジェクト指向の解釈は、元々のアラン・ケイによるSmalltalkの様式と、1983年に計算機科学者ビャーネ・ストロヴストルップが公開したC++の様式に二分された。前者ではメッセージングという概念が基礎にされ、後者ではSimula67由来の諸機能を加えた抽象データ型のスーパーセットが基礎にされていた。現在ではC++の様式がオブジェクト指向の標準になっている。
特徴
この記事には独自研究が含まれているおそれがあります。 |
OOPではクラスベースと呼ばれるスタイルが標準にされている。他にプロトタイプベースと呼ばれる後発のスタイルもあるが、こちらは少数派である。クラスベースのOOP言語は、Smalltalk様式とC++様式で二分されており、C++の方がずっと多数派である。C++様式は、静的型付けと動的型付けの分類で大別されている。それらの主な特徴を箇条書きするとこうなる。
- クラスベース - クラスのインスタンス化でオブジェクトを構築する。
- プロトタイプベース - オブジェクトのクローンでオブジェクトを構築する。クラスとインスタンスの相対性をオブジェクトから撤廃して、プロトタイプでオブジェクトを体系化している。動的型付け中心。動的バインディングによる多態性。
本稿では最も標準的なクラスベースのC++様式の静的型付けを基準にして説明する。
参考: 後述の「カプセル化」「継承」「ポリモーフィズム」の3項目は、「オブジェクト指向の三大要素」または「3大特徴」などと言われることもあるが、広く認められたものではないし、「オブジェクト指向プログラミングの特徴」なのか「オブジェクト指向言語に求められる機能」なのかという点も曖昧である。
クラスとインスタンス
OOPの要点であるクラスは、データ構造とそれを扱うための操作・振る舞いをひとまとめにした一種のプログラムモジュール機能として定義されており、その実装はSimula67由来の継承と動的ディスパッチを加えた抽象データ型のスーパーセットにされていることが多い。クラスのデータ構造はレコード型や構造体に似た書式で定義されることが多く、データ構造の要素は言語ごとにフィールド、プロパティ、属性、メンバ変数などと呼ばれている。クラスに定義される操作・振る舞いはメソッドやメンバ関数などと呼ばれる。メソッドとは、特定の対象に所属させることを前提にしてThis参照を多相シンボルにしたディスパッチコードとしての関数/手続きを意味している。
OOP言語のクラスと、そうでない言語でのモジュールの違いを知ることは、OOPを理解する上でも重要になる。どちらも手続きとデータの複合体であるが、クラスの第一の特徴はそれに継承が備えられていることであり、次にThis参照の機構と、継承構造上の内包的な動的ディスパッチである。その次になる情報隠蔽と、定義と実装に分離しての抽象化の機構は、OOP以前のモジュラルプログラミングからのものである。振る舞い抽象を担っているインターフェースの概念もそちらが先例であったが、データ抽象を担っているカプセル化(情報隠蔽とThis参照の融合)の採用はOOPが最初である。継承構造上の内包的な動的ディスパッチによるサブタイピング(=ポリモーフィズム)はOOP発祥であるが、それを純粋化すると前述のインターフェースになる。OOP以前の構造化開発のモジュールには情報隠蔽はあるが、継承はなく、カプセル化やポリモーフィズムといった抽象化の諸機構も持たない。なお、振る舞いサブタイピングは多重ディスパッチのOOPでは軽視されており、カプセル化は動的型付けのOOPではしばしば軽視されている。従ってクラスを特徴付けている要素とは即ち継承であり、継承はプログラムの再利用性と保守性を高めるためのメカニズムと定義されている。
C++様式のクラスベースはオブジェクトを、型理論に沿った型(type)と型付け値(term)に分離している。その型の一種であるユーザー定義型(user defined type)がクラスにされており、その型付け値がインスタンスになっている。ユーザー定義型とは構造体と同じものであり、OOPでは手続き(のポインタ)も構造体メンバにされている。ユーザー定義型はオブジェクト型とも呼ばれるが、このオブジェクト型と後節の「オブジェクト」の意味上の繋がりはない。クラスはインスタンスのひな型であり、インスタンスはクラスを実体化したものである。実体化=インスタンス化とは対象クラスの全変数内容を決定し、ヒープ領域の基底アドレス(This)を定めた上でそこにメモリ展開するという作業を指している。コンパイル時にその構成が確定されるユーザー定義型は静的型付けであり、実行時にもその構成を変更できるユーザー定義型は動的型付けである。なお、Smalltalk様式のクラスベースは型理論の依存型に似ている。依存型は型と値の境界がない型付けである。
オブジェクトとは
クラスベース言語での「オブジェクト」は、インスタンスを指す用語として説明されているが、このような重複語になった背景にはOOP原点のSmalltalkの設計がある。Smalltalkは、全てのプログラム要素はオブジェクトであり、全てのオブジェクトはクラスのインスタンス化であり、クラスもまたオブジェクトであり、オブジェクトは他のオブジェクトと相互作用(interaction)すると定義していた。そこではクラスもまたメタクラス(型)のインスタンス化(型付け値)になり、そのメタクラスもまた他のメタクラスのインスタンス化になっていたので、クラスとインスタンスは即ちオブジェクトの性質(相対性)を指すための言葉になっていた。これがC++様式では簡素化されて、クラスとインスタンスは型と型付け値の役割に固定され、メタクラスはリフレクション機能に固定されたので、クラスとインスタンス化の相互再帰が失われたオブジェクトは、相互作用できるインスタンスを指しているままで放置された。相互作用とは各種演算と同義であり、Smalltalkのクラスは相互作用できる型付け値でもあるのでオブジェクトであるが、C++様式のクラスはただの型なので演算対象に出来ないことからオブジェクトではなくなっている。
Smalltalk方言のSelfを原点とするプロトタイプベースは、オブジェクトからクラスとインスタンスの相対性を無くしたスタイルである。数値・文字列・配列・関数・シンボル・構造体(オブジェクト型)といった基本的な型は備えられているが、これはオブジェクト種類の区別に特化されたものなので、型理論に沿ったクラスベースのそれとは厳密には異なっている。クラス性質を除去したオブジェクトは事実上のインスタンスに一元化されており、その全てが相互作用(interaction)する。オブジェクトの表現はスロット(シンボルとコンテンツのペアデータ)の可変長配列でなされており、オブジェクトの識別は専らダックタイピングによってなされる。クラス概念が無いのでサブクラス化とインスタンス化は成立せず、代わりにクローン(複製)によってオブジェクトの継承がなされており、クローンはインスタンス化の代替になる。複製元オブジェクトは、複製先オブジェクトのプロトタイプと呼ばれる。
データ構造とそれを扱うためのメソッド群を情報隠蔽の概念と合わせてモジュール化(パッケージ化)するという手法がカプセル化と呼ばれる。カプセル化されたメソッドは、This値が暗黙の先頭引数として常に渡されるように実装される。This値とはクラスのデータ構造をメモリ展開するためのヒープ領域の基底アドレスを指しており、インスタンス化時に確定されたThis値によってメソッドは専用のデータ構造にアクセスできる。専用メソッドを通してのデータ構造の閲覧と変更は、抽象データ型の考え方に沿ったデータ構造の抽象化を意味することになり、これはデータ抽象と呼ばれる。データ閲覧用メソッドはゲッター/アクセッサと呼ばれ、データ変更用メソッドはセッター/ミューテイタと呼ばれる。
情報隠蔽とはそのクラスのデータ構造の各要素および各メソッドを必要に応じて内部隠蔽するという概念である。内部隠蔽されたデータ要素とメソッドはそのクラス外部からのアクセスが禁止される。抽象データ型本来の形式ではデータ構造のみが隠蔽対象になるので、これはデータ隠蔽とも呼ばれる。隠蔽指定外のデータ要素とメソッドは外部公開されて、そのクラス外部からもアクセス可能になる。外部公開の範囲を指定する機能はアクセスコントロールと呼ばれており、これが内部隠蔽の仕組みを担っている。クラスのレキシカルスコープを基準にした段階的なアクセス許可範囲は可視性と呼ばれる。可視性は無制限・任意クラスグループ限定・派生クラスグループ限定・自クラス限定(内部隠蔽)の四段階がUMLでは標準にされている。
既存クラスのデータ/メソッド構成に、任意のデータ/メソッド構成を付け足して、既存構成+新規構成の新しいクラスを定義するという手法が継承と呼ばれる。また、各クラスの共通構成パートを括りだして特有構成パートと分離することでオブジェクトを分類体系化し、同時にその共通構成パートの記号化によってソースコード内の重複記述を削減する機能とも解釈される。これは差分プログラミング目的の継承であり、1990年代までは多用された。2000年代になるとSOLID(リスコフ置換原則)の重視に伴なって、既存構成に抽象メソッドを置いて新規構成にその実体メソッドを置くというメソッドオーバーライドを用いるための振る舞いサブタイピング目的の継承が要点にされるようになっている。データ/メソッドの新規構成ではなく、メソッドの実装内容を付与するための継承であり、この継承をUMLクラス図は特化(specialization)と定義している。
既存クラスはスーパークラス・親クラス・基底クラスなどと呼ばれ、新しいクラスはサブクラス・子クラス・派生クラスなどと呼ばれる。親と子は差分プログラミング重視で、基底と派生はサブタイピング重視で用いられる。継承できるクラスが一つに限られている単一継承を採用している言語と、継承できるクラスの数に制限がない多重継承を採用している言語がある。抽象メソッドを持つクラスは抽象クラスと呼ばれる。基底クラス側でリターン型とパラメータのみが定義されて実行コードブロックが未定義のままの抽象メソッドは、その派生クラス側の実装メソッドでオーバーライドされる。オーバーライドは遅延結合による多相な複雑系アルゴリズムを表現するオープン再帰(open recursion)のメカニズムにもなっている。
抽象メソッドのみで構成される純粋抽象クラスはインターフェースと呼ばれ、その継承をUMLクラス図は実装(implementation)と定義している。インターフェースの実装は、サブタイピングのIs-a関係を完全順守させるメカニズムである。モジュラルプログラミングが先例になる実装は、継承から派生への回帰と言えるものである。
また、具象メソッドをクラスに注入するという継承アプローチもあり、これはミックスイン(mix-in)と呼ばれる。mix-inでの具象メソッドの集合体はトレイトとされることが多い。トレイトは抽象メソッドも持てる。これに倣って2000年代のSOLID(依存性逆転原則)重視に伴ない、抽象メソッド集合体のインターフェースが具象メソッドも持てるようにされて事実上のトレイト化している。トレイトはOOP原点のSmalltalk処理系発案なので、これは実装からmix-inへの回帰と言えるものである。
異なる種類のクラスに共通の操作インターフェースを持たせてオブジェクトの振る舞いを抽象化するという手法がポリモーフィズム(多態性)と呼ばれる。OOPで語られる多態性はもっぱら継承構造を利用したサブタイプ多相を対象にしているが、アドホック多相もサポート的に用いられており、パラメトリック多相はコンポジションで用いられている。そのサブタイプ多相の設計としては、コンパイル時に確定されたメソッド名から呼び出されるプロセス内容が実行時に決定されるという仕組みを指しており、一つのメソッド名からその実行時状態に合わせた個別のメソッド処理が呼び出されるようにするという演繹的意味と、各クラスの同種機能メソッドを一つの共通メソッドにまとめて実行時状態に合わせたメソッド処理が呼び出されるようにするという帰納的意味がある。
その実装としてはメソッドオーバーライド機能を活用した仮想関数と、実行時パターンマッチング機能を活用した総称関数の二つが挙げられる。仮想関数はスーパークラスの抽象メソッドの呼び出しを、それをオーバーライドしたサブクラスの実体メソッドの呼び出しにつなげるという動的ディスパッチ機能である。総称関数はオブジェクトの実行時パターンマッチングを使用する独立関数であり、その引数にされた各オブジェクトの型(=クラス)の組み合わせに従って実行コードブロックを選択決定するという多重ディスパッチ機能である。同名アルゴリズムを個々の派生クラスのメンバ関数に分散記述するのが仮想関数であり、個々の派生クラスの同名アルゴリズムを一つの独立関数に一括記述するのが総称関数である。
コンポジションとデリゲーションとジェネリクス
コンポジション (合成)とデリゲーション(委譲)は、OOPでは継承と対比される機能である。継承(特化)はIs-a関係の暗黙委譲、合成はHas-a関係の明示委譲と読み替えることができる。合成とは、特定処理の委譲先になる部品クラスの1個以上を持たせたクラス構造であり、そのクラスがとある処理を要求されてそれに対応できるデータ/メソッドを持っていない場合は、それに対応できる部品クラスを選択して処理を委譲するという仕組みである。その要求判別と選択過程を自動サーチ化したものが継承であり、部品クラスを基底クラスに置き換えて暗黙の委譲先にしたものである。しかしその自動サーチは、クラス階層に分散配置されているデータ/メソッドのどれが実際にアクセスされるのかという把握を困難にしたので、ここで差分プログラミング用途の継承の欠点が取り沙汰されるようになり、2000年代になるとクラスの機能拡張と分類体系化では、継承と合成の使い分けが重視されるようになった。また、Has-a関係はUMLクラス図では合成(composition)と集約(aggregation)に分けられている。
ジェネリクス(総称化)は、オブジェクトのデータ構造を汎用化して、それに汎用アルゴリズムの数々を適用できるようにした技術である。様々なデータ要素を内包するコンポジション(合成)オブジェクトはコンテナ(List/Set/Mapなど)と呼ばれ、その要素の型を型変数化することで汎用的なデータ構造を表現し、その要素の型はコンテナのインスタンス化時に与えられる型パラメータによって決定される。ジェネリクスはデータ構造とアルゴリズムを個別化しての柔軟なコンポーネント性を促進させ、反復子の方法論を確立した。また、圏論の圏に見立てられたコンテナおよび対象に見立てられたデータ要素の入れ子構造と、射に見立てられたサブタイピングの融合は、共変性と反変性の手法に発展した。
動的ディスパッチとメッセージパッシング
動的ディスパッチは、OOPのポリモーフィズムの原型機能であり、コンパイル時に確定されたメソッド名から呼び出されるメソッド内容(実行コードブロック)が、実行時に選択決定(ディスパッチ)される仕組み全般を指している。メソッドに与えられた各引数の型の組み合わせに従って、実行コードブロックが選択決定される仕組みのシングルディスパッチと多重ディスパッチを包括している。先頭引数の型パターンマッチング固定でディスパッチされるのはシングルになり、そうでないならば多重になる。例えば仮想関数はThisのシングルディスパッチである。
メッセージパッシングでは引数の型に加えて、メソッド名も実行時に解釈される要素にされており、そこでただのシンボルとして扱われるメソッド名はセレクタと呼ばれている。セレクタはメッセージ式を基本文とするSmalltalk様式のOOPで用いられる。object selector: param
ような書式でオブジェクトの共通窓口になるメッセージレシーバーに、セレクタと引数値で構成されたメッセージが送られる。また、object.call(method_name, param)
のような書式でオブジェクトの共通窓口関数をコールするのもメッセージパッシングと呼ばれる。これはRPCやORBなどの分散オブジェクト技術で用いられている。メソッド名(関数名)も実行時に解釈されるという特徴を指してメッセージパッシングと呼ぶ。メッセージレシーバーでは、渡されたセレクタのパターンマッチングに従って実行コードブロックがディスパッチされることが多く、OOP原点のSmalltalkは、引数とThisが与えられるその実行コードブロックをメソッドと呼んでいた。
インターフェースとトレイト
インターフェースは、カプセル化と継承のサブタイピング用法を促進した機能である。情報隠蔽とデータ抽象とメソッド抽象を合わせて表現する。クラスから見たインターフェースは自身のサービスのモデル化である。OOPのインターフェースは基本的には抽象メソッドのみで構成される抽象型と定義されている。その実装方式は純粋抽象クラスが基本形にされており、インスタンス化はできない継承専用クラスになって多重継承が前提にされている。インターフェースの抽象メソッドは、その実装クラスの同名具象メソッドでオーバーライドされる。インターフェースの各抽象メソッドはセッター・ゲッター・プロセスとして動作し、その実装内容は隠蔽されて実行時ごとに決定される。その代表使用例はソフトウェアコンポーネント間の相互通信媒体である。
OOP原点のSmalltalk処理系由来のトレイトは、クラスへの機能注入を目的にしたメソッドの集合体であり、トレイトの多重継承を扱う作法はミックスインと呼ばれている。トレイトの継承はインクルードともされ、これはUMLクラス図の特化と実装ではない第三の継承概念になるので標準OOP路線からは外れていた。2000年代のSOLID(依存性逆転原則)でトレイトは再評価され、機能注入用の具象メソッド及びメソッド間の共有状態といった特性が、従来のインターフェースに融合されるようになっている。
2004年以降に広まったSOLIDの依存性逆転原則は、OOPのパラダイムシフト的な潮流になっている。リスコフ置換原則による継承の差分プログラミング否定とサブタイピング重視を皮切りにし、開放閉鎖原則によってクラスのサブタイピングはインターフェースの実装へと進んだが、今度はインターフェースが具象メソッドも所持可能にされて事実上のトレイト化へと進んでいる。トレイトと融合されたインターフェースは、依存性の注入(DI)におけるDependency表現の抽象型として使われるようになった。インターフェース分離原則によって機能分化された個々のインターフェースを、それぞれ専用のオブジェクトに注入して[疑問点 ]その特定の機能と状態を受け持たせて、それらオブジェクトの専門的動作を注入インターフェースに依存させることが、抽象への依存=依存性の逆転になる[疑問点 ]。専門的な機能と状態が依存側に分離されたオブジェクトは、PlainOld(ごく普通)と言われる。オブジェクトの方は複数のインターフェースを注入指名できることから、ここでオブジェクトのイミュータブル性も取り沙汰されるようになった。PlainOldオブジェクトとDependencyインターフェースのコンテナは、従来の分散オブジェクト(EJBなど)コンテナに代わる分散システム・アプローチになっている。
依存性逆転原則によるこれらの潮流はいわゆる関数型の導入にも影響している。2000年代のOOP言語で採用されるようになったラムダ式や第一級関数やデリゲートは、上述のDependencyインターフェースのミクロ化である。イミュータブル書式は、PlainOldオブジェクトの作法に倣っている。
依存性逆転原則が訴える「抽象への依存」とは、メソッドを通してデータを操作するというデータ抽象と、基底メソッドを派生メソッドでオーバーライドするというメソッド抽象へのアンチテーゼであり、前者ではメソッドが具象データに、後者ではメソッドが詳細メソッドに依存していることへの反駁であった[要検証 ]。依存性逆転原則[要検証 ]下のメソッドは一切の専門的動作を外されて、上述のインターフェースの注入指名と、注入インターフェースとのデータ送受に特化される。サーバーやデータベースからの抽象体であるインターフェースの注入後に始めて専門的動作が可能になるので、これが抽象への依存になる。インターフェースはその動作専用の状態も保持できるので、それらを複数注入されるオブジェクトはイミュータブルな方が堅牢とされた。PlainOldにDependencyが注入される作法は、オブジェクトに機能オブジェクトがメッセージングされる作法に似ており、依存性の注入はSmalltalk処理系由来のトレイトの遅延結合をなぞっているので、OOPの思想は結局のところSmalltalkに行き着くようである。
歴史
1954年に初の高水準言語・FORTRANが登場すると、開発効率の劇的な向上と共にソフトウェア要求度も自然と高まりを見せてプログラム規模の急速な拡大が始まった。それに対応するために肥大化したメインルーチンをサブルーチンに分割する手法と、スパゲティ化したgoto命令を制御フロー構文に置き換える手法が編み出され、これらは1960年に公開された言語「ALGOL60」で形式化された。当時のALGOLはアルゴリズム記述の一つの模範形と見なされたが、それと並行して北欧を中心にした計算機科学者たちはより大局的な観点によるプログラム開発技法の研究を進めていた。
Simulaの開発(1962 - 72)
1962年、ノルウェー計算センターでモンテカルロ法シミュレーションを運用していた計算機科学者クリステン・ニゴールは、ALGOL60を土台にしてProcessと呼ばれるコルーチン機構を加えたプログラミング言語「Simula」を制作し、続けてその拡張にも取り組んだ。ニゴールの同僚で、1963年にSimulaを汎用機UNIVAC系統上で運用できるように実装した計算機科学者オルヨハン・ダールは、Processにローカル変数構造を共有する手続き(サブルーチン)を加えてパッケージ化する言語仕様を考案し、これは任意の変数と手続きをまとめるプログラムモジュールと同類の機能になった。程なくしてALGOL60コンパイラに準拠していての限界を悟ったニゴールとダールは、1965年からSimulaを一から再設計するように方針転換した。その過程で彼らは、計算機科学者アントニー・ホーアが考案して1962年のSIMSCRIPT(FORTRAN用のスクリプト)に実装していたRecord Classを参考にしている。Record Classはソースコード水準の抽象記号を、各汎用機に準拠したマシンコード水準の実装符号に落とし込む段階的データ構造のプログラム概念であった。これをモデルにした継承と、その継承構造を利用した仮想手続き(仮想関数)の仕組みも考案され、上述のパッケージ化されたProcess(モジュール)に継承と仮想手続きの両機能を加えたものを「クラス」と定義し、クラスをメモリに展開したものを「オブジェクト」と定義する言語仕様がまとまり、1967年に「Simula67」が初公開された。オブジェクトという用語は、MITの計算機科学者アイバン・サザランドが1963年に開発したSketchpadの設計上にあったObjectが先例であった。SketchpadはCADとGUIの原点として知られており、後述のSmalltalkのモチーフの一つにもなっている。Simula67コンパイラはまず汎用機UNIVAC上で運用され、翌年から汎用機バロースB5500などでも稼働されて北欧、ドイツ、ソ連の各研究機関へと広まり、1972年にはIBM汎用機System/360などにも導入されて北米全土にも広まった。その主な用途は離散事象および物理シミュレーションであった。
構造化プログラミングの提唱(1969 - 75)
1960年代半ばになるとプログラム規模の際限なき拡大に伴なうソフトウェア開発の難航が頻発するようになり、いわゆるソフトウェア危機問題が取り沙汰されるようになった。その解決に取り組んだ計算機科学者エドガー・ダイクストラは、1969年のNATOソフトウェア工学会議で「構造化プログラミング」という論文を発表しトップダウン設計、段階的な抽象化、階層的なモジュール化、共同詳細化(抽象データ構造と抽象ステートメントを連携させて具象化する概念)といった技法を提唱した。その論旨はプログラム正当性検証技術の確立であり、数学証明に倣った視点でソースコードを適切に分割して抽象化することが勧められていた。しかしこの構造化プログラミングは後に曲解されて、制御フロー構文(順次・分岐・反復)を勧める論旨で世間に広まることになり、ダイクストラ本来の論旨であったプログラムモジュールを抽象化して扱おうとする考え方は当時の世間には伝わらなかった。共同詳細化は抽象データ構造を専用ステートメントを通して扱おうとする概念であり、Simula67の手続きを通してクラス内の変数にアクセスする仕組みに似ていた。段階的な抽象化と階層的なモジュール化は、SIMSCRIPTの段階的データ構造とSimura67の継承による階層的クラス構造が先例になっていた。ダイクストラ、ホーア、ダールの三者は1972年に『構造化プログラミング』と題した共著を上梓しており、その階層的プログラム構造という章の中でダールは、Simulaの設計理念を更に明らかにした。
1974年にMITの計算機科学者バーバラ・リスコフは「抽象データ型」という概念を提唱し、上述のモジュールの共同詳細化をその振る舞いによって意味内容が決定される抽象データという考え方でより明解に形式化した。1975年に計算機科学者ニクラウス・ヴィルトは、モジュール機能を主題にした言語Modulaと共にモジュラルプログラミングを提唱した。このパラダイムでは、モジュールを仕様定義とコード/データ実装に分離しての、前者による抽象化と後者の情報隠蔽が備えられて、これはインターフェースの実装という概念の先例になっている。また、1970年代後半からIBM社を中心にした研究者たちが(エドワード・ヨードンなど)サブルーチンモジュールとデータ構造を連携させる構造化のパラダイムを発表し、こちらではモジュールの抽象指向は倦厭されて具象的な段階的詳細化が重んじられた。当時は具象指向の方に軍配が上がり、構造化開発は1980年代までのソフトウェア開発の主流になっている。このようにいささか奇妙ではあるが、Simulaのクラスとオブジェクトというプログラム概念は、プログラムモジュールの登場からモジュラルや構造化開発へといった進化の流れとは関係なく、しかもその前段階において生まれていた。
Smalltalkとオブジェクト指向の誕生(1972 - 81)
Simula発のProcessとクラスの示した可能性は、パロアルト研究所の計算機科学者アラン・ケイによる「メッセージング」という考え方のヒントになった。ケイはプログラム内のあらゆる要素をオブジェクトとして扱い、オブジェクトはメッセージの送受信でコミュニケーションするという独特のプログラム理論を提唱した。それには従来の関数呼び出しをセレクタの実行時解釈に置き換えて積極的な委譲を推進するメッセージ式と、プログラムコードとしても解釈できるデータ列を送信してそれを任意のタイミングで評価(eval)することで新たなデータを導出できるなどのアイディアが盛り込まれていた。これらの遅延結合パラダイムは非同期通信や単方向通信への可能性をも開いており、この発想の背景にはLISPの影響があった。メッセージを駆使するオブジェクトの構築には、Simula発のそれにプラトンのイデア論を重ね合わせたクラスとインスタンスの仕組みが導入された。オブジェクトとメッセージングの構想に基づいて開発された「Smalltalk」はプログラミング言語とGUIフレームワークを併せたものとなり、1972年にデータゼネラルNova上での1000行程度のBASICを使った試作(概念実証)を経て、翌1973年に新開発されたゼロックスAlto上で本格稼働された。Smalltalkの設計を説明するためにケイが考案した「オブジェクト指向」という用語はここで初めて発信された。またケイのメッセージング構想はMITの計算機科学者カール・ヒューイットに能動的なプロセス代数を意識させて[4]、1973年発表のアクターモデルのヒントにもなっている。しかし委譲の多用とデータ列が常にコード候補としても扱われる処理系は、当時のコンピュータには負荷が大きく実用的な速度を得られないという問題にすぐ直面した。Smalltalk-74(新たに開発されたBitBLTを使った高速描画版Smalltalk-72)からSmalltalk-76の過程で、やむなくメッセージは(多くの場合)関数の動的コールに、メソッドはパターンマッチ処理から単なる関数へ置き換えられるなど構想時の柔軟さが失われるほど最適化された。また、ケイの留保した継承機構[5]も導入されてオブジェクトは抽象データ型の性格も有するようになった。
1980年のSmalltalk-80は、元々はメッセージを重視していたケイを自嘲させるほど同期的で双方向的で手続き的なオブジェクト指向へと変貌していた。それでも動的ディスパッチと委譲でオブジェクトを連携させるスタイルは画期的であり、1994年に発表されるデザインパターンの模範にもされている。1981年に当時の著名なマイコン専門誌『BYTE』が、Smalltalkとその理念であるオブジェクト指向を紹介して世間の注目を集める契機になったが、ケイの思惑に反して技術的関心を集めたのはクラスの仕組みの方であった。オブジェクト指向は知名度を得るのと同時に、Simula発のクラス(継承と動的ディスパッチ)および抽象データ型(データ抽象とデータ隠蔽)にマウントされて解釈されるようになり、それらのコンセプトがケイの構想とは無関係であったことから、オブジェクト指向の定義はケイの手を離れて独り歩きするようになった。
C++の開発と普及(1979 - 88)
Simulaを研究対象にしていたAT&Tベル研究所の計算機科学者ビャーネ・ストロヴストルップは、1979年からクラス付きC言語の制作に取り組み、1983年に「C++」を公開した。C++で実装されたクラスは、Simula由来の継承と仮想関数に加えて、段階的なレキシカルスコープの概念をクラス構造に応用したアクセスコントロールを備えていた。C++で確立されたアクセスコントロールは情報隠蔽によるデータ抽象を提示したが、コードスタイル上ほとんどザル化されており、その理由からストロヴストルップ自身もC++は正しくない(not just)オブジェクト指向言語であると明言している。1986年にソフトウェア技術者バートランド・メイヤーが制作した「Eiffel」の方は、正しいオブジェクト指向を標榜してクラスのデータ抽象を遵守させるコードスタイルが導入されていた。クラスメンバ(フィーチャー)は属性/手続き/関数の三種構成で、手続きで属性を変更して関数で属性を閲覧するという形式に限定されており、これは抽象データ型に忠実な実装であった。アクセスコントロールはC++のとは異なるクラス指名方式にされ、仮想関数機能は延期手続き/関数として実装された。
1986年からACMがOOPSLAを年度開催するようになり、オブジェクト指向は従来の構造化開発に代わる技術として明確に意識され始めた。OOPSLAのプログラミング言語セクションでは、抽象データ型を基礎にしたクラス・パラダイムが主要テーマにされ、それを標準化するための数々のトピックが議題に上げられている。モジュール分割、抽象化、再利用性、階層構造、複合構成、情報隠蔽、実行時多態、動的束縛、総称型、永続性、並行性、自動メモリ管理といったものがそうであり、参画した識者たちによる寄稿、出版、講演を通して世間にも広められた。そうした潮流の中でストロヴストルップはデータ抽象の重要性を訴え、リスコフは基底と派生に分けたデータ抽象の階層構造の連結関係(置換原則)について提言した。契約による設計と開放閉鎖原則を提唱するメイヤーが1988年に刊行した『オブジェクト指向ソフトウェア構築』によるEiffel式のクラス理論は高く評価され、Eiffelを現行の模範形とする声も多く上がった。ただしこれは学術寄りの意見でもあったようで、世間のプログラマの間では厳格なEiffelよりも、柔軟で融通の利くC++の人気の方が高まっていた。他方でアラン・ケイのメッセージ・メタファに忠実であろうとする動きもあり、1984年に制作された「Objective-C」はC言語をSmalltalk方向に拡張してメッセージ式を平易化した言語であった。1987年にパロアルト研究所で誕生した「Self」は、Smalltalkのクラスベース設計をより柔軟に平易化したプロトタイプベースを編み出している。これらのメッセージレシーバーは、静的メソッド選択優先の動的ディスパッチ機構という方式でほぼ形式化された。メッセージレシーバーの仕組みは遠隔手続き呼出しやオブジェクト要求ブローカーの実装に適していたので、分散システムとオブジェクト指向の親和性を認識させることになった。
コンポーネントとネットワーク(1989 - 97)
ネットワーク技術の発展に連れて、データとメソッドの複合体であるオブジェクトの概念は、分散システム構築のための基礎要素としての適性を特に見出される事になり、IBM社、Apple社、サン社などが1989年に共同設立したOMGは、企業システムネットワーク向け分散オブジェクトプログラミング規格となるCORBAを1991年に発表した。その前年にマイクロソフト社はウェブアプリケーション向けの分散オブジェクトプログラミング技術となるOLEを発表し、1993年にはCOMと称するソフトウェアコンポーネント仕様へと整備した。このCOMの利用を眼目にしてリリースされた「Visual C++」「Visual Basic」はウェブ時代の新しいプログラミング様式を普及させる先駆になった。この頃にデータ抽象、データ隠蔽、アクセスコントロールおよびインターフェースによるプログラムの抽象化は、総じてカプセル化の概念でまとめられるようになった。クラスの継承が最もオブジェクト指向らしい機能と見なされていたのが当時の特徴であった。継承構造を利用した振る舞いサブタイピング及び動的ディスパッチは多態性という用語に包括された。こうしていわゆるオブジェクト指向の三大要素がやや漠然と確立されている。1996年にサン社がリリースした「Java」は三大要素が強く意識されたクラスベースであり、その中の分散オブジェクト技術はBeansと呼ばれた。類似の技術としてApple社もMacOS上でObjective-Cなどから扱えるCocoaを開発している。また、1994年から96年にかけて「Python」「Ruby」「JavaScript」といったオブジェクト指向スクリプト言語がリリースされ、従来の静的型付けに対する動的型付けと、クラスベースに対する新しいプロトタイプベースの認知度を高めている。
抽象化を旨とするオブジェクト指向ではそのプログラミング自体の抽象化も積極的に推進されている。80年代後半から立ち上げられたオブジェクト指向分析(OOA)やオブジェクト指向設計(OOD)の各手法から導き出される概念モデルを、多角的にチャート化ないしダイアグラム化するための数々のオブジェクト指向開発方法論がOOPSLA界隈の識者たちから発表されるようになり、そこで用いられる形式言語はオブジェクトモデリング言語と呼ばれた。これはプログラミングのためのプロトタイプ(ひな型)として重視され、オブジェクト指向ではモデリング言語とプログラミング言語が並んでソフトウェア開発の両輪になった。1994年にモデリング言語によるGOFデザインパターンが初回発表され、これはオブジェクト指向教育で非常に重視されるようになった。1995年にモデリング言語の標準化を企図したUMLがOOPSLAで初回発表され、1997年にOMGの標準モデリング言語として採用された。同年にデザインパターンを帰納的に分析して九原則にまとめたGRASPが発表されている。
オブジェクト指向言語一覧
ここではクラスベースの記述は省略している。
- Simula67 1967年 静的
- Smalltalk 1972年 動的・メッセージ式・純粋系
- C++ 1983年 静的
- Objective-C 1984年 静的と動的・メッセージ式
- Object Pascal 1986年 静的
- Eiffel 1986年 静的・純粋系
- Self 1987年 動的・プロトタイプベース・メッセージ式・純粋系
- Modula-3 1988年 静的
- Common Lisp(CLOS) 1988年(ANSI規格化は1994年) 動的・多重ディスパッチ・メタオブジェクト
- Oberon-2 1991年 静的
- Dylan 1992年 動的・多重ディスパッチ
- Lua 1993年 動的・プロトタイプベース
- Python 1994年(ver1.0) 動的
- Delphi 1995年 静的
- Ada 1995年からOOP化 静的
- Perl 1995年からOOP化 動的
- Ruby 1995年 動的・純粋系
- Java 1996年(ver1.0) 静的
- JavaScript 1996年(first released) 動的・プロトタイプベース
- OCaml 1996年 静的・関数型
- Squeak 1996年 動的・メッセージ式
- ECMAScript 1997年 動的・プロトタイプベース
- C# 2000年 静的と動的
- Visual Basic.NET 2001年 静的
- D言語 2001年 静的
- Io 2002年 動的・プロトタイプベース・メッセージ式・純粋系
- COBOL 2002年からOOP化 静的
- FORTRAN 2003年からOOP化 静的
- R言語 2003年からOOP化 動的・関数型・多重ディスパッチ
- Scala 2003年 静的・関数型
- Groovy 2003年 動的
- PHP 2004年からOOP化 静的と動的
- F# 2005年 静的・関数型
- MATLAB 2008年からOOP化 動的・関数型
- Go 2009年 静的
- Rust 2010年 静的・関数型
- Kotlin 2011年 静的
- Ceylon 2011年 静的
- Dart 2011年 静的・関数型
- Elixir 2011年 動的・関数型
- Julia 2012年 動的・関数型・多重ディスパッチ
- TypeScript 2012年 静的と動的・関数型
- Swift 2014年 静的・プロトコル指向
- Raku 2015年 静的と動的
用語一覧
ここでは日本語ページ化されている項目のみ列挙している。
- クラスベース
- プロトタイプベース
- メッセージ
- クラス
- インスタンス
- フィールド
- プロパティ
- メソッド
- インスタンス変数
- クラス変数
- コンストラクタ
- デストラクタ
- カプセル化
- 継承
- 多態性
- This
- Is-a
- Has-a
- サブタイピング
- ダックタイピング
- ミックスイン
- スーパークラス
- サブクラス
- 抽象クラス
- メタクラス
- オーバーライド
- 仮想関数テーブル
- 菱形継承問題
- 仮想継承
- インターフェース
- トレイト
- 抽象型
- オブジェクト型
- 型クラス
- 委譲
- 多重ディスパッチ
- ダブルディスパッチ
- オーバーロード
- アノテーション
- コンテナ
- ボックス化
- ジェネリクス
- 共変性と反変性
- モジュール
- ソフトウェアコンポーネント
- リフレクション
- モンキーパッチ
- メッセージ転送
- イミュータブル
- 型システム
- オブジェクト指向分析設計
- オブジェクト指向モデリング
- UML
- 契約による設計
- GOFデザインパターン
- GRASP
- SOLID
- リファクタリング
- 依存性の注入
脚注
- ^ Kindler, E.; Krivy, I. (2011). Object-Oriented Simulation of systems with sophisticated control. International Journal of General Systems. pp. 313–343.
- ^ Lewis, John; Loftus, William (2008). Java Software Solutions Foundations of Programming Design 6th ed. Pearson Education Inc.. ISBN 978-0-321-53205-3, section 1.6 "Object-Oriented Programming"
- ^ “At Utah sometime after Nov 66 when, influenced by Sketchpad, Simula, the design for the ARPAnet, the Burroughs B5000, and my background in Biology and Mathematics, I thought of an architecture for programming. It was probably in 1967 when someone asked me what I was doing, and I said: "It's object-oriented programming".”Alan Kay on the Meaning of “Object-Oriented Programming”
- ^ “Our research has concentrated on the development of a rigorous model of computation based on relationship among computational events. The development of this model has been greatly influenced by Seymour Papert's “little people” model of computation, a seminar given by Alan Key at M.I.T. on an early version of Smalltalk, and the work of Church, Fischer, Landin, on formalisms based on the lambda calculus.”Induction and Meta-evaluation
- ^ “I didn't like the way Simula I or Simula 67 did inheritance (though I thought Nygaard and Dahl were just tremendous thinkers and designers). So I decided to leave out inheritance as a built-in feature until I understood it better. ”Alan Kay on the Meaning of “Object-Oriented Programming”