OOPは, 以下の 3 大要素を基本とすると一般に言われている:
カプセル化 (Encapsulation)
ポリモーフィズム (Polymorphism)
継承 (Inheritance)
これらは, すべて [クラス] を導入することによって実現する.
OOP におけるクラスあ, ある [モノ] の振る舞いを記述するために必要な変数と手続きを定義する. C 言語の構造体は, データ構造を定義する, すなわちデータの関連を明確にしてまとめる機能を持っている. クラスは, データ構造に加えてそのデータに対する手続きを明確にする (実際に関係のある手続きを記述するかどうかはプログラマ次第だが) と考えることもできる.
クラスに属する変数は [メンバ変数], 手続きは [メソッド] などと呼ばれる. 例えば, [データを出し入れするモノとしてのファイル] をクラスで表現すると:
クラス名: File
メンバ変数: ファイル識別子
手続き: open, write, read, close
となる. クラスは, 実際には単なるデータ構造と手続きの集合にすぎないので, 利用するにはデータを保存するメモリ領域を確保する必要がある. この作業を [インスタンス化] といい, メモリ領域が確保され手続きが実行可能な状態になっているものをインスタンスという.
一つのクラスから複数のインスタンスをできる. クラスは, 従来の型に相当し, インスタンスは変数と考えるとわかりやすい. 例えば, int 型をクラスと考えると:
クラス名: int
メンバ変数: 整数
手続き: 代入, +, -, *, /, などの演算
と定義できる. インスタンス化は:
int i, j;
と記述でき, i 及び j が インスタンス (とできるもの) である. このとき, 整数を保存するメモリ領域がそれぞれ確保されている.
File を C++ で定義すると, 以下のようになる:
class File{ // File クラスを定義する
public: // public: 以下のメソッドやメンバ変数はクラスの外からアクセス可能
File();
void open(char* filename);
void write(char* data);
void read(char* out_data);
void close();
FILE* file_descriptor;
};
File クラスの C++ における利用例を示す:
File file1, file2; // ファイルのインスタンス file1, file2 を生成する
file1.open("test1.txt"); // test1.txt を開く. これによりインスタンス file1 はファイル test1.txt を表現するオブジェクトとなる
file2.open("test2.txt"); // test2.txt を開く. これによりインスタンス file2 はファイル test2.txt を表現するオブジェクトとなる
file1.write("some strings"); // file1 に対して write メソッドを実行する. すなわち test1.txt に文字列を書き込む
file2.write("some strings"); // file2 に対して write メソッドを実行する. すなわち test2.txt に文字列を書き込む
メンバ変数は, インスタンス毎にそれぞれメモリ領域が用意される (インスタンスに独立の値が保持される) ので, インスタンス変数とも呼ばれる.
クラスの機能として, 先に述べた [カプセル化], [ポリフィズム], [継承] が実現されている (C++, JAVA などほとんどのオブジェクト指向言語)
カプセル化とは, クラスのメソッドやメンバ変数をクラスの外から見えなくすることである. [隠蔽] とも呼ばれる. 例えば, 上記の File クラスのメンバ変数 file_descriptor は, クラスの外 (すなわちクラスの利用者) から書き換えられるべきではない. 従って, カプセル化によりクラス外からのアクセスを禁止するべきである. 言語仕様により, メンバ変数へのアクセスを制限することにより, メンバ変数の値に対する責任をクラスに持たせることができる. つまり, クラス設計者はクラスの外で何が起きるかを気にする必要がない. 責任の範囲を限定することでコーディングやメンテナンスの効率がよくなる.
ポリモーフィズム (多態性) は, 呼び出しの手続きを共通化する仕組みである. C 言語では, ある関数の定義しそれを呼び出すと, 必ず定義されたルーチンが呼び出される. OOP では, 同一の (シンタックスが同じの) メソッド呼び出しが複数のルーチンに結びつけられる. 実際には, オブジェクトの実態に応じて呼び出されるメソッドが変わることが多い.
例えば:
file1.write("some sttring");
というメソッド呼び出しが, File クラスの write メソッドの呼び出しであるとは限らない. 実際には, file1 オブジェクトの中身 (何クラスのインスタンスか) による. この仕組みは, 後術する [継承] によることが多い.
継承とは, あるクラスが持つメンバ変数とメソッドを他のクラスが受け継ぐことである. 一般には, あるクラスの機能を拡張して新しいクラスを定義する場合に用いる. 継承基のクラスをスーパークラス, 継承先クラスをサブクラスと呼ぶ.
例として, あらかじめデータをメモリに読み出しておくことで読み込みを高速化する BufferedFile クラスを定義することを考えよう. (一般にディスクからの読み込みより, メモリからの読み込みのほうが圧倒的に高速である)
クラス定義は, 以下のようになる:
クラス名: BufferedFile
メンバ変数: ファイル識別子, バッファ用配列
メソッド: open, write, read, close
比較のため File クラスの定義を再度示す.
クラス名: File
メンバ変数: ファイル識別子
メソッド: open, write, read, close
並べてみると, バッファ用の配列が追加されたのみで両者の定義はほご同じであることがわかる. また, Buffered クラスの実装には, File クラスのメソッドの呼び出しが利用できそうである. このように場合に継承を用いる.