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

オブジェクト指向プログラミング (Object-Oriented Programming: OOP)とは, “object” と 呼ばれるモノと object 間の相互作用を定義することでソフトウェア開発を行うプログラミング技術である.

現在の高水準言語はほとんどオブジェクト指向をサポートしている. オブジェクト指向プログラミングは, 単なるプログラミング手法なので, 必ずしもオブジェクト指向のプログラム言語 (JAVA や C++ など) を利用する必要はなく, 例えば C 言語でも可能である.

なぜオブジェクト指向を用いるのか?

オブジェクト指向によって, ソフトウェア開発を楽にするため, 生産性を上げるために利用する.

なぜオブジェクト指向が生まれたのか?

ソフトウェアの進化は, ハードウェアの進化に伴って起こる. 以前は, ハードウェアが貧弱だったので, 計算機にとって効率の良いプログラムコードを人間が工夫して書く必要があった. しかし, コンピュータの処理能力の向上に伴い, ソフトウェアが肥大化し, メンテナンスが困難となった. そこで, 実行効率 (計算速度) を少しだけ犠牲にしてより人間にとって理解しやすいプログラミング言語や技術が開発された.

プログラミング言語の進化の歴史は, この繰り返しである.

C 言語の時代は, [構造化プログラミング] が用いられた. 構造化プログラミングとは, 大きなプログラムを小さなプログラム (サブルーチン) の集合に分割していくことでプログラムの保守を容易にする, という考え方である.

小さなプログラムは, C 言語では [関数] として表現される.

例として, ファイル (test.txt) から 1 行ずつ読み込み, 特定の文字列(a string) が含まれている行だけを表示するプログラムを考えよう:

int main(){
  char *line;
  openFile("test.txt");

  while( line == readLine() ){
    if( checkContainString("a string", line) ){
      printLine(line);
    }
  }
}

ここで,

openFile: 実行結果として, ファイルをオープンする. (入力) ファイル名 (出力) なし.

readLine: 既に開かれているファイルから 1 行分読み込む (入力) なし (出力) 読み込んだ文字列, ファイルの終端に達したら NULL を返す.

checkContainString: 文字列 B が 文字列 A を含むかを調べる (入力) 文字列 A, B (出力) 検索結果 (含む = 1, 含まない = 0)

printLine: 文字列を表示する (入力) 表示する文字列.

これらの四つの関数は, それぞれに与えられた機能が実現できればよい. また, これらの関数が正しく動作していれば, このプログラム全体が正しく動作する事は明らかである. さらに各関数内でも, 標準ライブラリなどの他の関数が呼び出される. プログラムは, 標準ライブラリが正しく動作することを前提にプログラムする. 標準ライブラリを利用しているだけでも, 構造化プログラムであると言える.

こうしてみると, 旧来方式である [構造化プログラミング] もなかなか優れものであることがわかる. 実は, オブジェクト指向プログラミングは構造化プログラミングではない. OOP でも, 局所的には構造化プログラミングを用いるのが普通である. たとえば, 標準 (もしくは非標準でも) ライブラリ関数を一切使わずにプログラミングすることは困難である.

では, なぜ OOP が考えだされたのか. もう一度, 上記のプログラム例を見てみよう. このプログラムは, 一つのファイルを開いて読み込むことを前提としている. 作成したサブルーチン群を利用して, 2つのファイルの差分を出力するプログラムが作れるだろうか?

一つの解決策としては, openFile 関数から開いているファイルをする値が返るようにし, readLine にその番号を引数として与えるようにする. こうすると複数のファイルを同時に扱えるようになる. しかし, こうなると標準ライブラリの fopen, fgets を使うのと変わりがない. さらに, ユーザ (関数の利用者) にファイルの識別子を用意してもらい, それを毎回引数で渡してもらうことを強制することになる. この問題は, openFile と readLine が内部でファイル識別子をやり取りするためにグローバル変数を使っている (であろう) ことに起因する. C 言語では, 引数を用いずに関数間でデータを移動するには, グローバル変数を使う必要があるからである.

グローバル変数は, アプリケーション内に一つしか存在しない, つまりアプリケーションに対して割り当てられる変数である. しかし, ファイル識別子は, ファイル毎に管理されるベキ物である. OPP は, このようなもの (object) に対して割り当てられるべき値を自然に記述することができる. 結果的に, グローバル変数の利用を抑制する.

もう一つの問題は, コードの再利用の問題である. 上記のコード例では, フィアルから文字列を読み込むことを前提としている. しかし, 現在はネットワーク社会であり, ネットワーク経由のデータを扱うことは必須である. まったく同じコードで, ある時はファイル, ある時はネットワークからというように動的に入力を切り替えることができるだろうか. C 言語では, 関数名は, グローバルな識別子である. すなわちアプリケーションに対して一つである. しかし, ストレージ (ファイル等) を開く, データを読み出す, といった処理は [データを出し入れするもの] に対して割り当てられるべきである. OPP は, このようなモノ (object) に対して割り当てられるべき処理を自然に記述することができる. そして, そのモノをグループ化して共通な手続き (例えば, 開く, 読み込む, など) を定義しておくことで, 呼び出す側のコードはそのままで, 処理内容を変えることも可能である. これは, 結果的にサブルーチンだけではなく, サブルーチンを呼び出す側のコードの再利用を可能にする.

まとめると, OPP は:

グローバル変数の利用を抑制する
コードの再利用の幅を広げる

ことなど目的としていると捉えることができる.

モノに対して割り当てる変数や手続きを定義するモノが [クラス] である.