コンテンツにスキップ

4-2. オブジェクト指向の原則

  • クラスとインスタンスの基本
  • カプセル化・継承・ポリモーフィズムの意味
  • オブジェクト指向が、プログラムを整理するのにどう役立つか
  • JavaScript のクラス例を通して、概念を具体的に見る方法

1. なぜオブジェクト指向が必要なのか

Section titled “1. なぜオブジェクト指向が必要なのか”

プログラムが小さいうちは、変数と関数だけでもある程度は書ける。
しかし機能が増えてくると、次のような問題が起きやすくなる。

  • どのデータに、どの処理が対応するのか分かりにくい
  • 同じような処理があちこちに散らばる
  • 一部の変更が、別の場所へ予想外の影響を与える

そこで登場する考え方の 1 つがオブジェクト指向である。

オブジェクト指向では、データと、そのデータに対する操作を近くにまとめて考える

データだけが散らばる状態
- name
- price
- updatePrice()
- validatePrice()
1つの対象としてまとめる
Book
- title
- price
- updatePrice()
- validatePrice()

これにより、「この処理は何に対する処理か」が見えやすくなる。


オブジェクト指向を理解する最初の入口が、クラスインスタンスである。

用語意味
クラスオブジェクトの設計図
インスタンス設計図から作られた実物
class Book
┌─────────────┬─────────────┐
│ Book("Git") │ Book("DB") │
└─────────────┴─────────────┘

JavaScript の例で見ると、次のようになる。

class Book {
constructor(title) {
this.title = title;
}
}
const book1 = new Book("Git Basics");
const book2 = new Book("DB Basics");
  • Book がクラス
  • book1book2 がインスタンス

this は、いま操作しているそのインスタンス自身を表す。

book1.titlebook1 のタイトル、book2.titlebook2 のタイトルになる。
つまり同じクラスから作られていても、インスタンスごとに持つ値は異なる。


カプセル化とは、内部データと操作をまとめ、外から勝手に壊されにくくする考え方である。

たとえば口座残高を管理するとき、外部コードが好き勝手に値を書き換えられると危険である。
そこで「残高はメソッド経由でしか変えない」という形にすると、安全性が上がる。

class BankAccount {
#balance = 0;
deposit(amount) {
if (amount <= 0) {
throw new Error("amount must be positive");
}
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
  • 残高そのものは #balance に閉じ込められている
  • 入金は deposit() というルール経由でしか行えない
  • 不正な値はメソッド内で検査できる

つまりカプセル化は、「外から見えなくすること」だけでなく、状態を壊れにくくすることが目的である。


継承とは、共通する性質を親クラスにまとめ、子クラスがそれを引き継ぐ仕組みである。

class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
speak() {
console.log(`${this.name} barks`);
}
}

この例では、DogAnimal を継承している。

  • name という共通データは親から受け継ぐ
  • speak() は子クラス側でより具体的に書き換えられる
  • 共通部分をまとめやすい
  • 似た対象をグループとして扱いやすい
  • 変更箇所を集約しやすい

ただし継承は「何でも使えばよい便利機能」ではない。
まずは共通部分を親へまとめる考え方を理解することが大切である。


ポリモーフィズムとは、同じ操作名で呼び出しても、実体によって振る舞いが変わるという考え方である。

class Cat extends Animal {
speak() {
console.log(`${this.name} meows`);
}
}
const animals = [new Dog("Pochi"), new Cat("Tama")];
for (const animal of animals) {
animal.speak();
}

このコードでは、animal.speak() という同じ呼び出しをしているのに、実際の出力は次のように変わる。

Pochi barks
Tama meows

これがポリモーフィズムの基本である。

ポリモーフィズムがないと、呼び出し側で大量の ifswitch を書いて「犬ならこれ、猫ならこれ」と分けたくなりやすい。
しかしオブジェクト側へ振る舞いを持たせると、呼び出し側は共通の操作だけを知っていればよい


カプセル化 : データと操作をまとめ、壊れにくくする
継承 : 共通部分をまとめて引き継ぐ
ポリモーフィズム : 同じ操作名で対象ごとに振る舞いを変える

これら 3 つは別々の知識ではなく、次のように組み合わさる。

  • クラスで対象の構造を定義する
  • カプセル化で状態を守る
  • 継承で共通部分をまとめる
  • ポリモーフィズムで呼び出し側を単純にする

実務では、オブジェクト指向を使う目的は「難しい設計用語を覚えること」ではなく、変更に強く、読みやすいコードにすることである。


クラスは設計図、インスタンスは実物である。
Bookbook1 は同じものではない。

親クラスの共通ルールを活用しつつ、子クラスで必要な差分を持たせる考え方である。

ポリモーフィズムは「同じ結果」ではない

Section titled “ポリモーフィズムは「同じ結果」ではない”

同じ操作名で呼べることが重要であり、結果は対象ごとに違ってよい。

カプセル化は隠すことそのものが目的ではない

Section titled “カプセル化は隠すことそのものが目的ではない”

目的は、内部状態を壊れにくくし、ルールを守りやすくすることである。


キーワード説明
クラスオブジェクトの設計図
インスタンスクラスから作られた実物
thisいま操作しているインスタンス自身
カプセル化データと操作をまとめ、状態を守る考え方
継承共通部分を親クラスへまとめて引き継ぐ仕組み
ポリモーフィズム同じ操作名で対象ごとに振る舞いを変える考え方
メソッドクラスの中に定義された関数

演習問題 に取り組んで理解を確認しよう。

理解できたら 4-3. 例外処理・ログの考え方 へ進もう。