はじめに
新卒Webエンジニア1年目の自分がオブジェクト指向について勉強したく「オブジェクト指向でなぜつくるのか 第3版」を読んだのでアウトプット&メモ。
読んだ本↓
1. 「オブジェクト指向でなぜつくるのか 第3版」の目次
- 第1章 オブジェクト指向はソフトウエア開発を楽にする技術
- 第2章 オブジェクト指向と現実世界は似て非なるもの
- 第3章 OOPを理解する近道はプログラミング言語の歴史にあり
- 第4章 OOPは無駄を省いて整理整頓するプログラミング技術
- 第5章 メモリの仕組みの理解はプログラマのたしなみ
- 第6章 OOPがもたらしたソフトウエアとアイデアの再利用
- 第7章 汎用の整理術に化けたオブジェクト指向
- 第8章 UMLは形のないソフトウエアを見る道具
- 第9章 現実世界とソフトウエアのギャップを埋めるモデリング
- 第10章 擬人化して役割分担させるオブジェクト指向設計
- 第11章 オブジェクト指向から生まれたアジャイル開発
- 第12章 オブジェクト指向を使いこなそう
2. オブジェクト指向の歴史と起源
2-1. オブジェクト指向の起源
オブジェクト指向を提唱した人の名前
アラン・ケイ
起源
オブジェクト 指向の起源は、1967年にノルウェーで考案されたSimula 67( シミュラ 67)というプログラミング 言語。
アラン・ケイ氏の率いる米ゼロックス社のチームが開発したSmalltalkに引き継がれて、「オブジェクト 指向」というコンセプトとして確立。
オブジェクト指向が提唱される以前はどうしていたのか
オブジェクト指向が普及する以前は、対象とするシステム全体の機能をとらえ、それを段階的に詳細化して、より小さな部分に分解していく「機能中心」の開発手法が主流でした。
機能中心とモノ中心の開発手法の違い
機能中心
- 仕様変更や機能追加が起きた場合の修正範囲が広範囲になりやすい
部品の依存性が高いため、ある改修をした時に関係のないところに影響が出る可能性が高い- ソフトウェアの再利用が難しい
部品の依存性が高いため、影響範囲が広く。ある機能だけ再利用する事が難しい
- ソフトウェアの再利用が難しい
モノ中心
- ソフトウェアの保守がしやすい
- 部品の独立性を高めているため、影響範囲を把握しやすい
- ソフトウェアの再利用がしやすい
- 部品の独立性を高めているから、影響範囲範囲が小さい。つまり他のシステムでも再利用しやすい
2-2. オブジェクト指向ができるまでの歴史
黎明期には、機械語でプログラムを書いていた
# 機械語の例
A10010
8B160210
01D0
A10410
プログラミングの最初の一歩、アセンブリ言語
# アセンブリ言語の例
MOV X, Y
ADD Y, 2
無機質な機械語を人間がわかりやすい記号に置き換えて表現。
アセンブリを書いたら、コンパイルするアセンブラに読み込ませて機械語を生成。
高水準言語の発明でプログラムはより人間に近づく
# 高水準言語の例(FORTRAN, COBOLなど)
Z = X + Y
FORTRANが1957年、COBOLが1960年に登場。
高級言語の登場により、プログラミングの生産性や品質は大きく向上した。
1960年代後半にはNATOの国際会議で「20世紀末には世界の総人口がプログラマになっても、増大するソフトウェアへの需要に追いつかない」というソフトウェア危機が宣言された。
わかりやすさを重視する構造化プログラミング理論の登場
ソフトウェア危機に対応するために、オランダのダイクストラ氏によって構造化プログラミングが登場。
構造化プログラミングの考え方は「正しく動作するプログラムを作成するためには、分かりやすい構造にすることが重要である」というもの。分かりやすくするために具体的には以下のような方法が挙げられた。
- GOTO文廃止(ロジックを基本三構造(順次進行、条件分岐、繰り返しのみで表現する)
- サブルーチンの独立姓を高める
GOTO文は、処理を指定した位置へと移動させるためのジャンプ文の一種。
GOTO文を乱用してしまうと、処理が順次進行ではなくなり、プログラムを行ったり来たりするようになるため、複雑になってしまいます。
そういった複雑化を避けるため、構造化プログラミングではGOTO文を廃止しました。
サブルーチン自体はすでに発明されていたが、当時はサブルーチンを利用する際はグローバル変数を使って値を受け渡しすることが一般的だった。グローバル変数はプログラムの保守性やデバッガビリティを低下させる。
この問題に対応してサブルーチンを汎用的に利用するため、ローカル変数と引数の値渡しの仕組みが考案された。
グローバル変数は、プログラムのどこからでも使われる可能性があるので、改修時にグローバル変数を変更する際は影響範囲を確かめるために全てのロジックを確かめる必要があった。
構造化言語に残された問題は、グローバル変数問題と貧弱な再利用
グローバル変数問題
ローカル変数は、サブルーチンが終わると消える一時的な変数。実行後も超えて保持する場合は、グローバル変数として保持せざる負えなかった。
貧弱な再利用
構造化言語で再利用できるのは当時、サブルーチン(関数)のみでした。
サブルーチンだけでは再利用出来る範囲が狭すぎるので、より大きな再利用できる部品のような仕組みが必要でした。
↑を解決するために生まれたのがOOP。
3. オブジェクト指向の3大要素
3-1. クラス
クラスは種類、インスタンスは具体的なモノ。
以下、クラスとインスタンスの例。
クラス | インスタンス |
---|---|
犬 | ポチ、 コロ、 タロー、 ショコラ、 キヌ…… |
国 | 日本、 韓国、 中国、 アメリカ、 イギリス…… |
コードでクラスの例。
↓Dogクラスを定義
class Dog {
String name;
Dog(String name) {
this.name = name;
}
String cry(){
return "ワン"
}
}
↓インスタンス化してメソッドを呼ぶ
Dog pochi = new Dog("ポチ")
Dog taro = new Dog("タロー")
SYstem.out.prointln(pochi.cry());
SYstem.out.prointln(taro.cry());
cry()
メソッドを呼び出すことを相手にメッセージを送って仕事を頼む様子に似ていること からメッセージパッシングと呼ぶ。
クラスは、まとめて隠してたくさん作る仕組み
- サブルーチン と 変数 を「まとめる」
- クラス の 内部 だけで 使う 変数 や サブルーチン を「 隠す」
- 1つ の クラス から インスタンス を「 たくさん 作る」
### 3-1-1. クラスの特徴1:まとめる
↓構造化プログラミングの場合
int fileNum;
void openFile(String pathName) { /* ロジックは省略 */ }
void closeFile() { /* ロジックは省略 */ }
void readFile() { /* ロジックは省略 */ }
↓オブジェクト指向プログラミングの場合
class TextFileReader {
int fileNum;
void openFile(String pathName) { /* ロジックは省略 */ }
void closeFile() { /* ロジックは省略 */ }
void readFile() { /* ロジックは省略 */ }
}
結びつきの強い(複数の)サブルーチンと(複数の)グローバル変数を1つのクラスに「 まとめる」ことができる。これにより次のメリットが得られる。
- 部品の数が減る。
- メソッド(サブルーチン)の名前づけが楽になる。
- メソッド(サブルーチン)が探しやすくなる。
3-1-2. クラスの特徴2: 隠す
↓TextFileReaderクラスのfileNum変数をprivateへ。
class TextFileReader {
private int fileNum;
void openFile(String pathName) { /* ロジックは省略 */ }
void closeFile() { /* ロジックは省略 */ }
void readFile() { /* ロジックは省略 */ }
}
fileNum変数へのアクセスは、クラス内部にあるメソッドだけに限定。
変数がグローバルじゃなくなる。
- クラス に 定義 し た 変数 と メソッド( サブルーチン) を、 他 の クラス から「 隠す」 こと が できる。 これ により、 プログラム の 保守 性 悪化 の 元凶 と なる グローバル 変数 を 使わ ず に プログラム を 書く こと が 可能 に なる。
3-1-3. クラスの特徴3:たくさん作る
↓TextFileReaderクラスからreader1, reader2を作る。
TextFileReader reader1 = new TextFileReader();
TextFileReader reader2 = new TextFileReader();
reader1.open("/home/test1.txt");
reader1.open("/home/test2.txt");
char ch1;
char ch2;
ch1 = reader1.read();
ch2 = reader2.read();
reader1.close();
reader2.close();
いったんクラスとして定義すると、実行時にそこからいくつでもインスタンスを作ることができる。これにより、ファイル、文字列、顧客情報など、同種の情報を複数同時に扱う処理であっ ても、そのクラス内部のロジックをシンプルにできる。
3-2. ポリモーフィズム
ポリモーフィズムはメッセージの送り方を共通にする。
日本語では、「多態性」。ポリモーフィズム(多態姓)の説明で個人的に分かりやすかった説明を引用。
引用1.
この仕組みをひと言で表現すると「類似したクラスに対するメッセージの送り方を共通にする 仕組み」と言えます。「相手が具体的にどのクラスのインスタンスであるかを意識せずにメッセージを送れる仕組み」と言ってもよいでしょう。
引用2.
現実世界になぞらえて説明すると次のようになります。先ほど犬にcry(鳴け/泣け)という メッセージを送ると、「ワン」と応える例を示しました。このメッセージを受け取る側が赤ん坊 だったら「オギャー」と泣き、カラスなら「カー」と応じるというものです
コードでポーリモーフィズムの例。
↓ポリモーフィズムの準備
class Animal{
// 具体的な鳴き方は定義しない
abstract String cry();
}
class Baby extends Animal {
String cry() {
return "オギャー"
}
}
class Dog extends Animal {
String cry() {
return "ワン"
}
}
ポリモーフィズム は メッセージ を 送る 側 が 楽 を する ため の 仕掛け。 この 例 で 言う と、「 cry( 泣け/ 鳴け)」 と 指示 する 側 は 相手 が 誰 でも 同じ 指示 の 仕方 で すませる こと が できる。
ここでのポイントはexecuteメソッドの引数が、Animal(動物)クラスのインスタンスになっ ていること。これにより相手が赤ん坊だろうと、犬だろうとカラスだろうと何が来ても大丈夫。
Class Trainer {
void execute (Animal animal) {
System.out.println(animal.cry())
}
}
もう一例。
↓ポリモーフィズムの準備
class TextReader {
void openFile() { /* ロジックは省略 */ }
void closeFile() { /* ロジックは省略 */ }
void readFile() { /* ロジックは省略 */ }
}
Public class NetworkReader extends TextReader {
public void open() { /* ロジックは省略 */}
public void close() { /* ロジックは省略 */ }
public void read() { /* ロジックは省略 */ }
class TextFileReader extends TextReader {
int fileNum;
public TextFileReader(String pathName) { /* ロジックは省略 */ }
void openFile(String pathName) { /* ロジックは省略 */ }
void closeFile() { /* ロジックは省略 */ }
void readFile() { /* ロジックは省略 */ }
}
↓のgetCountメソッドの引数には、TextFileReader, NetworkReaderのどちらも指定できる
int getCount(TextReader reader){
int charCount = 0;
while(true){
char = reader.read(); // ポリモーフィズムを使って文字を読む
// 終了条件になったらループから抜けるロジックは省略
charCount++;
}
return charCount;
}
3-3. 継承
継承の説明で個人的に分かりやすかった説明を2つ引用。
引用1.
継承をひと言で表現すると「モノの種類の共通点と相違点を体系的に整理する仕組み」です。 オブジェクト指向では「モノの種類」はクラスのことですから、表現を変えると「似たもの同士 のクラスの共通点と相違点を整理する仕組み」とも言えます。
引用2.
オブジェクト指向では、全体集合をスーパークラス、部分集合をサブクラスと呼びます(これ は集合論において、スーパーセット、サブセットと呼ぶのと同様です)。
この継承関係は、動物の分類体系だけでなく、医師を内科医、外科医、眼科医、歯科医に分類 したり、会社員を営業職、技術職、事務職に分類したりと、現実世界のさまざまな状況に当てはめることができそうです
↓例
共通の性質である「行動する」「吠える/鳴く」は、動物クラスに定義し、固有の性質である「 出産する」は哺乳類クラスに、「飛ぶ」は鳥クラスに定義する。
コードで継承の例↓
class Animal{
// 具体的な鳴き方は定義しない
abstract String cry();
void move();
}
class Baby extends Animal {
String cry() {
return "オギャー"
}
void bear() { /* ロジックは省略 */}
}
class Bird extends Animal {
String cry() {
return "カー"
}
void fly() { /* ロジックは省略 */}
}
まとめ
「オブジェクト指向でなぜつくるのか 第3版」を読んでの以下2つについてのメモでした。
- オブジェクト指向の起源と歴史
- オブジェクト指向の3大要素
「進化したオブジェクト指向プログラミングの仕組み」「オブジェクト指向から生まれたアジャイル開発手法」についても、別記事でQiitaに投稿しようと思います。
僕自身、新卒1年目で「オブジェクト指向でなぜつくるのか 第3版」を読みましたが、新卒2、3年目の人やこれからエンジニアになりたい人は読んで損のない本だと思いました。