クラス (コンピュータ)
オブジェクト指向プログラミングにおけるクラス(英: class)[注釈 1]は、オブジェクトを生成するための設計図あるいはひな形に相当するものである。抽象データ型の一つ。クラスから生成したオブジェクトの実体のことをインスタンスという。
クラスには、クラス自身またはクラスのインスタンスが保持するデータと、データに関連したオブジェクトの振る舞いを記述できる。プログラミング言語によっては、それぞれにアクセス修飾子を指定できる。統一モデリング言語 (UML) のクラス図では、データのことを「属性」、振る舞いのことを「操作」と呼ぶ。Javaなどでは、データのことを「フィールド」、振る舞いのことを「メソッド」と呼ぶ。C++などでは、データのことを「メンバー変数」、振る舞いのことを「メンバー関数」と呼ぶ。
クラスは、クラスベースのオブジェクト指向プログラミングの基本である。また、オブジェクト指向プログラミングにおけるカプセル化・継承・ポリモーフィズムなどを、クラスベースのオブジェクト指向プログラミングにおいてはクラスを必要に応じて適宜使って実装する。一方、カプセル化・継承・ポリモーフィズムなどを、プロトタイプベースのオブジェクト指向プログラミングにおいてはクラスを使わずに実装する。
プログラミング言語におけるクラスのサポートは、オーレ=ヨハン・ダールによってSimula 67において初めて導入された。この時点ではまだオブジェクト指向の概念や用語は確立されていなかったが、のちにSimulaの影響を受けたビャーネ・ストロヴストルップのC++[1]と、アラン・ケイのSmalltalkによってオブジェクト指向が再定義されることになる。
クラス設計のための基本概念
[編集]カプセル化 (encapsulation)
[編集]一般にどんなプログラムであれ、プログラム機能を提供するためにはデータを保有するだけではなく、データに対する操作ができなければならない。単に複数のデータをまとめる手段としては、C言語の構造体やPascalのレコード型といった形で従来の手続き型プログラミング言語においても提供されている。一方クラスは、データだけでなくそのデータに関連する操作もひとまとめにして管理する枠組みを提供する。
このように関連する変数や操作などをクラスの所属物として一つにまとめてしまうことを、クラスによる情報のカプセル化(encapsulation)と呼ぶ。適切なカプセル化により、データ構造やアルゴリズムなどを変更したとしても、変更箇所はカプセル化されたクラス領域内だけで済み、変更箇所がクラス外の関連ソースコード全体にまで散乱・波及してしまうことを防ぐことができる。
またアクセス修飾子 (access modifier) により、所属物に対して公開/非公開情報の区別をつけることで、クラス外部からクラス内に対して破壊的操作を加えることを防いだり、特定の機密データをクラス外部から見ることができないようにしたりするなど、外部に開放する情報に制限をつけることができる。カプセル化した上に公開/非公開情報の区別を加えることを情報隠蔽(information hiding)と呼ぶ[2][注釈 2]。
継承 (inheritance/extension/generalization)
[編集]継承(inheritance)または拡張(extension)とも呼ばれる。既存のクラスに基づき新たなクラスを構成する。その目的は、単純なクラスに基づいてもっと複雑なクラスを構成することである。また、複雑なクラスはそれを定義する単純なクラスに従属するという意味で、クラスに階層をつけることができるようになる[3][注釈 3]。継承の基になったクラスを親クラス/基本クラス/基底クラス/スーパークラスなどといい、継承してできたクラスを子クラス/派生クラス/サブクラスなどという。派生クラスのインスタンスはまた基本クラスのインスタンスとしても扱えるようになる(リスコフの置換原則)。継承により、後述のポリモーフィズムを実現することができるようになる。
UMLでは継承のことを汎化 (generalization) と呼んでいる。汎化とはスーパークラスによる抽象化であり、対義語の特化 (specialization) はサブクラスによる具象化を指す。
また、オブジェクト指向を効率よく使いこなすためには継承だけでなく集約 (aggregation)、委譲 (delegation) を理解する必要がある。
継承は、開放/閉鎖原則に基づき、単純な基本クラスからより複雑な派生クラスを構成する機構であり、コードの再利用と拡張を容易にする。逆に複雑なクラスの所属物のいくつかを除いて単純なクラスを構成しようとすると、コードの再利用と拡張を阻害することになる。
すなわち、最初から多数の所属物をカプセル化したり、基本クラスから継承するにしても多数の所属物を付け加えて極めて特化されたクラスを最初から作成してしまうと、途中でそれよりやや一般的なクラスが必要になっても代替させることができない。
複数の基本クラスを継承して一つの新しいクラスを派生させることを多重継承 (multiple inheritance) と呼ぶ。多重継承により、基となった全てのクラスの所属物は合わせて一つになり、全ての動作が組み合わさった新しい一つのクラスが構成される。ただし、実装の多重継承は二つの基本クラスの同名メソッドのオーバーライドによるコンフリクトを始めとするいくつかの問題点(菱形継承問題など)が指摘されている。したがって実装の継承は通例、一つのクラスに基づいてその拡張を行う単一継承を用いる。C++は多重継承を許可し、多重継承にまつわる問題の解決手段を仮想継承によって提供しているが、他の多くの言語、例えばJava、C#、D言語では実装の多重継承はサポートされておらず、インターフェイスの複数実装による型の多重継承のみサポートされている。ただしJava 8以降はインターフェイスのデフォルト実装により、実装の多重継承も限定的にサポートするようになった。なお、Simula 67は多重継承もインターフェイスもサポートしていなかった[4]。
C++ などの言語では、既存のクラスを継承したクラスを作ることで新たなメソッドの作成(追加)が可能となる。
ポリモーフィズム (polymorphism)
[編集]クラスを継承する際に、スーパークラスの振る舞いをサブクラスの振る舞いで上書きする(置き換える)ことをオーバーライドという。あるサブクラスのインスタンスがオーバーライドされた振る舞いを持つ場合、インスタンスの具体的な内容(クラス)が分からなくても、インスタンスに対してその振る舞いを実行するよう指示すれば、見かけがスーパークラスと同じ(すなわちインターフェイスが同じ)でありながら、インスタンスの実際のクラスに応じて実行される振る舞い(処理内容)を変えることができる。このようにして、見かけが一緒なのに動作が変わることをポリモーフィズム(ポリモルフィズム)/多様性/多態性/多相性などという。
Simulaにおけるクラス
[編集]ダイクストラの構造化プログラミングは、プログラムの大規模開発への道を開いたが、あくまで単一スレッド(single thread)計算機を前提としたトップダウン型開発方法であった。すなわち、プログラムのすべての機能は単線の計算プロセス上で実行する必要があり、たとえ甲と乙という汎用的な単機能を提供する検証済みのプログラムがそれぞれ独立に存在していても、両機能を実現するプログラムを作成するためには、ソースコードから該当機能部分を抜き出し、単線上に乗るように連接(concatenation)した上で、一つのプログラムとして正しく動作するように修正し、さらに再度検証しなければならない。
一方で、複数スレッド(multi thread)計算機においては、主プログラムから、甲と乙のプログラムなどの従プログラムをそれぞれ並列に実行させた上で、処理内容を従プログラムに(OSの機能などを仲介して)伝言受け渡し(message passing)して代わりに処理させることで、検証済みプログラムのソースコードに手を加えることなく、低コストで開発することができる(コルーチンを用いたプログラミング)[注釈 4]。
オーレ=ヨハン・ダールとアントニー・ホーアは、このような考え方の有効性を主張し[5]、上記のような一連の操作を一つの言語の中で完結させるための機構を提案した。それがクラスの構文である。
ダールとホーアは、まず主プログラムから従プログラムを並列呼び出しする際、読み込みするにあたって新たに(new)割り当てられたメモリ領域に限定して走る計算プロセスを実例(instance;インスタンス)と名付け、さらにその実例の集まり(class of instances)をそれが記述されたソースコードと同一視した。その上で、呼び出されたときだけではなく、存在し続ける従プログラムの実例のもとになる手続きをクラス(class)、その実例を(「クラスの実例」ではなく)改めてクラスの対象(object)と名付けた[6]。さらに、その考えに基づいてSimula 67にクラスの構文を実装した[注釈 5]。
脚注
[編集]注釈
[編集]- ^ 英語の class は、本来「分類」「種類」といった意味を持っている。
- ^ 多くのプログラミング言語ではフィールドやメソッドの定義とアクセス権の指定は同時になされるため、カプセル化と情報隠蔽はしばしば混同される。
- ^ これが、ダールとホーアの論文の題名である『階層的プログラム構造』である。ダール(1972)
- ^ ただし、随所にOSの機能を利用することになるため異なるOSへの移植性が低い上に、主プログラムと並列呼び出しする従プログラムが異なる言語で記述されている場合、複数の異なるコンパイラが必要となり、場合によっては複数の異なる言語を使用しなければならなくなってしまう。
- ^
言語仕様にクラス構文を導入することで以下のような利益が得られる。
- 主プログラムと従プログラムに相当するものが異なる言語で記述されることがない。
- 複数スレッド計算機のOSに依存した以下の一連の操作を言語内部で統一的に処理できるようになる
- 主プログラムからのメモリ割り当て
- 並列呼び出し
- 抽象データ型として表現される場合、OSを仲介した伝言のやりとりのような形式ではなく、体裁上は具体的データ型のデータに対する処理への引数渡し、処理返しとして取り扱い可能になる
出典
[編集]- ^ Bjarne Stroustrup. “A History of C++: 1979−1991”. 2019年2月2日閲覧。
- ^ 落水(1993) p.82
- ^ 構造化プログラミング(1975) p.226
- ^ INTRODUCTION TO SIMULA | WHAT IS WRONG WITH SIMULA ?
- ^ 構造化プログラミング(1975) pp.201-202
- ^ 構造化プログラミング(1975) p.202
参考文献
[編集]- オーレ=ヨハン・ダール, C.A.R. ホーア (1972), 階層的プログラム構造
- E.W.ダイクストラ、C.A.R.ホーア、O.-J.ダール 著、野下浩平,川合慧,武市正人 訳『構造化プログラミング』サイエンス社、1975年。 (収録)
- 川合 慧『システム プログラム』近代科学社〈コンピュータサイエンス大学講座〉、1982年。
- 落水 浩一郎『ソフトウェア工学実践の基礎』日科技連〈実践ソフトウェア開発工学シリーズ〉、1993年。
- Ole-Johan Dahl (2001), The Birth of Object Orientation: the Simula Languages