はじめに
この記事の目的は、オブジェクト指向プログラミングの概念をつかむこと。
オブジェクト指向は独立した概念ではなく、それまでのものを拡張して作られている。
そのためにまず、オブジェクト指向に至るまでのプログラミング言語の遷移を追う。
そして、オブジェクト指向を構成する3大要素を解説する。
プログラミング言語の歴史
1. 始まりは機械語
コンピュータは0と1しか理解できないため、
2進数や8、16進数の機械語で書いていた。
A10010
8B160210
01D0
A10410
上記は算術計算を実行する命令が書かれている。
2. プログラミング言語の登場
初のプログラム言語のアセンブリ言語によって、
コードを機械語に変換する処理(コンパイル)が必要になった。
MOV AX, X
MOV DX, Y
ADD AX, DX
MOV Z, AX
3. 高級言語の発明
コンピュータが理解する命令を1つずつ記述するのではなく、
人間が見て分かり易いまとまった命令ができるようになった。
z=X+Y !アセンブリ言語コードを高級言語で書いた場合
4. 構造化プログラミングの登場
プログラム構造をより解りやすくするため、以下3つを採用した。
1. GOTO文の廃止
2. ロジックを順次実行、条件分岐、繰り返しの3つの構造で表現
3. ローカル変数と値渡しの実装
GOTO文によるスパゲッティコードを回避することでコードの保守性が向上した。
またローカル変数と値渡しによってサブルーチンによるグローバル変数の汚染を防いだ。
GOTO文とは?:
コードの好きな場所に処理を移行できる。
つまり順次実行ではなくめちゃくちゃな順番で処理される。
int main() {
goto B;
A: printf("a"); goto C;
B: printf("b"); goto A;
C: printf("c");
}
bac
グローバル変数汚染:
グローバル変数はどこからでもアクセスできるため、
初期値から変更すると参照透過性が失われることによってエラーのもとになる。
サブルーチン自身が変数を持ち値を保持することでグローバル変数を使用せずに済む。
またサブルーチンから別のサブルーチンを呼び出す時やその処理結果(戻り値)を、
値渡しでやり取りすることによってローカル変数も汚染を防ぐことができる。
5. オブジェクト指向プログラミングの登場
オブジェクト指向の3つの仕組みであるクラス(カプセル化)と継承、
ポリモーフィズムにより、コードを部品として再利用可能にした。
再利用技術:
汎用性の高いコード部品をまとめて再利用する、
クラスライブラリやフレームワークなどが誕生した。
また、ソフトウェア開発時の具体的なコードではなく、
設計のノウハウやアイデアもデザインパターンとして再利用された。
オブジェクト指向プログラミング
オブジェクト指向はモノ中心の開発手法。
モノとは、属性(状態)と振る舞い(メソッド)をオブジェクトとしてまとめて
定義したもの。
そのオブジェクトを組み合わせてプログラムを作成する。
オブジェクト指向以前は機能中心で、
機能(関数)を組み合わせてプログラムを作成していた。
三大要素
オブジェクト指向プログラミングを構成する要素は、
クラス、継承、ポリモーフィズムの3つ。
1. クラス
クラスとは、サブルーチンと変数をまとめる仕組み。
クラスにまとめた(定義した)サブルーチンをメソッド、
変数をインスタンス変数という。
クラスを使用するには、クラスからインスタンスと呼ばれるオブジェクトを生成する。
1つのクラスから複数のインスタンスを作ることができる。
class Animal {
constructor(name) {
this.name = name;
}
cry() {
console.log(`${this.name}`);
}
}
//1つのクラスから複数のインスタンスを作成
const animal1 = new Animal('インスタンス1');
const animal2 = new Animal('インスタンス2');
const animal3 = new Animal('インスタンス3');
animal1.cry();
animal2.cry();
animal3.cry();
インスタンス1
インスタンス2
インスタンス3
2. 継承
既存クラスをもとに新しい別クラスを作成することで、
既存クラスに定義された変数やメソッドなどを別クラスに受け継がせる仕組み。
//スーパークラス
class Animal {
constructor(voice) {
this.voice = voice;
}
cry() {
console.log(`${this.voice}`);
}
}
//Animalクラスを継承したDogクラス
class Dog extends Animal {
constructor() {
super('ワン');
}
}
const dog = new Dog();
dog.cry();
Dogクラスはcryメソッドを定義していないがAnimalクラスを継承しているため、
Animalクラスに定義されているcryメソッドやメンバ変数を使用できる。
ワン
3. ポリモーフィズム
複数のクラスが持つ同名のメソッドの呼び出し方を共通(一元)にする仕組み。
class Animal {
constructor(voice) {
this.voice = voice;
}
cry() {
console.log(`${this.voice}`);
}
}
class Dog extends Animal {
constructor() {
super('ワン');
}
}
class Cat extends Animal {
constructor() {
super('ニャー');
}
}
const animals = [
new Dog(),
new Cat()
];
animals.forEach(animal => animal.cry());
cryメソッドが各クラスが持つ同名メソッドであり、
それを呼び出しているのが最終行のanimals.forEach(animal => animal.cry());
である。
cryメソッドを持つクラスを更に追加しても、呼び出す処理の修正をする必要がない。
ワン
ニャー
仕組み
ポリモーフィズムを実現する方法の1つに、メソッドテーブルを定義する方法がある。
メソッドテーブルとは:
クラスに定義されているメソッドが格納されているメモリアドレスを示す
ポインタを記録している。
全てのクラスにメソッドテーブルを保持させる。
メソッドテーブルには、クラスに定義された全てのメソッドのポインタ一覧が
記録されている。
あるクラスを継承したクラスには、親クラスが持つメソッドテーブルも継承させる。
子クラス独自のメソッドがある場合、継承したメソッドテーブルにそのメソッドの
ポインタを追加する。
また、子クラスでメソッドをオーバーライドしている場合、子クラスのもので上書する。
この仕組みによってオーバライドしているメソッドを、
全く同じロジックで呼び出すことができる。