「オブジェクト指向をわかりやすく説明した記事が見つからない」。そう思ってここに来た方が多いはずです。
読んでも読んでも、たとえ話が急にコードに変わって置いていかれる。カプセル化、継承、ポリモーフィズム。用語だけが増えていく。
その気持ち、痛いほど分かります。私も独学時代、この壁の前で三か月ほど足踏みしました。
私は普段、PHPとLaravelでWebアプリを開発しています。実務歴は2年と少し。今は毎日クラスを書いていますが、最初はまったく理解できませんでした。この記事ではRPGのキャラクターという例えを最後まで一本で通します。たとえ話とコードを1行ずつ対応させるので、途中で迷子になりません。
Section 01オブジェクト指向をわかりやすく言うと「モノ」を中心に考えるプログラミング
細かい定義は後回しにします。まず全体像を1分でつかみましょう。
オブジェクト指向とは?
オブジェクト指向とは、プログラムを「モノ」の集まりとして組み立てる考え方です。英語ではObject-Oriented Programming、略してOOPと呼ばれます。
ここでいう「モノ」とは、データとふるまいをセットにした部品のことです。たとえばRPGの勇者。名前やHPというデータを持ち、攻撃するというふるまいを持っています。この2つをバラバラに管理せず、ひとつの箱にまとめる。それがオブジェクト指向の出発点です。
難しく感じる原因は、たいてい説明の順番にあります。定義から入ると抽象的すぎて頭に残りません。先に「勇者という箱を作る」というイメージを持つだけで、理解の速度が変わります。
手続き型との違い
オブジェクト指向の前に主流だったのが「手続き型」です。処理を上から順番に書いていくスタイルを指します。まずは手続き型で勇者を表現してみます。
Python
# データと処理がバラバラに置かれている
hero_name = "タロウ"
hero_hp = 100
def attack(name):
print(name + "の攻撃!")
attack(hero_name)タロウの攻撃!
これでも動きます。問題は、キャラクターが増えたときです。魔法使い、僧侶、敵のスライム。変数と関数が延々と増え続けます。どの変数がどの関数とペアなのか、誰にも分からなくなります。
オブジェクト指向は、この散らかりを解決します。名前・HP・攻撃を「勇者」という箱にまとめてしまう。すると、扱う対象が変数の山ではなくキャラクター単位になります。
| 観点 | 手続き型 | オブジェクト指向 |
|---|---|---|
| 考え方の単位 | 処理の手順 | モノ(オブジェクト) |
| データと処理 | 分かれている | ひとつにまとまる |
| 得意な規模 | 小さいプログラム | 中〜大規模 |
| 変更したいとき | 影響範囲を追いにくい | 該当のクラスだけ直せる |
なぜオブジェクト指向が生まれたのか
理由はシンプルで、プログラムが大きくなりすぎたからです。数百行なら手続き型で十分です。しかし数万行になると、人間の記憶力では管理できません。
私が実務で最初に触ったLaravelのプロジェクトは、ファイル数が数百ありました。それでも読めたのは、機能ごとにクラスが分かれていたからです。「ユーザーに関する処理はUserクラスにある」と当たりがつく。この安心感がオブジェクト指向の実利です。
まずはここまでで十分です。「モノ単位で考える」という一点だけ、頭の隅に置いて次に進んでください。
関連記事:プログラミングとは?未経験からWebエンジニアを目指す始め方
Section 02オブジェクト指向をわかりやすく理解するならRPGのキャラクターで考えよう
ここからは、たとえ話で骨組みを作ります。使うのはRPGのキャラクターです。この例えを記事の最後まで使い続けます。
クラスは「キャラクターの設計図」
クラスとは設計図です。実体ではありません。
RPGでいえば「勇者という職業の仕様書」にあたります。勇者は名前を持つ。HPを持つ。攻撃ができる。そう決めた紙が1枚あるイメージです。その紙自体はゲーム画面に出てきません。
ここが初心者のつまずきポイントです。クラスを書いただけでは何も起きません。設計図を書いただけで家が建たないのと同じです。
インスタンスは「実際に作ったキャラクター」
設計図から実際に生み出したものがインスタンスです。
「勇者」の設計図から、名前がタロウのキャラクターを作る。名前がハナコのキャラクターも作る。この2人がインスタンスです。同じ設計図から作られていますが、中身のデータは別々です。タロウがダメージを受けても、ハナコのHPは減りません。
ゲームのキャラメイク画面を思い出してください。あの画面で「決定」を押した瞬間が、インスタンスの生成です。
プロパティは「レベル・HPなどの能力」
プロパティは、そのキャラクターが持つデータです。名前、HP、攻撃力、レベル。ステータス画面に並んでいる数値がこれにあたります。
属性やフィールドと呼ばれることもあります。呼び方は言語によって変わりますが、指しているものは同じです。「そのモノが持っている情報」と覚えれば困りません。
メソッドは「攻撃・回復などの行動」
メソッドは、そのキャラクターができる行動です。攻撃する、回復する、逃げる。戦闘中のコマンド欄に並ぶものだと考えてください。
関数との違いを聞かれることがあります。ざっくり言えば、クラスの中に書かれた関数がメソッドです。それ以上の区別は、今は気にしなくて大丈夫です。
4つの用語を一枚で整理します。
| 用語 | RPGでいうと | 役割 |
|---|---|---|
| クラス | 勇者の設計図 | 仕様を定義する |
| インスタンス | 勇者タロウ本人 | 設計図から作られた実体 |
| プロパティ | HP・攻撃力・レベル | 持っているデータ |
| メソッド | 攻撃する・回復する | できる行動 |
この表を一度スクショしておいてください。以降のコードで迷ったら、ここに戻れば必ず解けます。
Section 03RPGの例を実際のコードに置き換えてみよう
ここが多くの人の離脱ポイントです。たとえ話は分かった、でもコードになると分からない。橋をゆっくり架けていきます。
Pythonでクラスを書いてみる
まず設計図を書きます。Pythonを使うのは、記号が少なく読みやすいからです。
Python
class Hero:
# インスタンスを作るときに呼ばれる初期設定
def init(self, name, hp):
self.name = name # プロパティ:名前
self.hp = hp # プロパティ:HP
# メソッド:攻撃する
def attack(self):
print(self.name + "の攻撃!")見慣れない部分を3つだけ解説します。
class Hero:は「勇者の設計図をここから書きます」という宣言です。__init__は、キャラを作る瞬間に自動で動く初期設定です。ステータスの初期値を決める場所だと思ってください。selfは「このキャラ自身」を指します。self.nameは「このキャラの名前」です。
この時点では、まだ画面には何も出ません。設計図を書いただけだからです。
インスタンスを生成してみる
設計図から、実際のキャラクターを作ります。
Python
hero = Hero("タロウ", 100) # 勇者タロウが誕生
enemy = Hero("ハナコ", 80) # 別のキャラも同じ設計図から作れる
hero.attack() # メソッドを呼ぶ
print(hero.name, hero.hp) # プロパティを見る
print(enemy.name, enemy.hp)タロウの攻撃!
タロウ 100
ハナコ 80設計図は1枚。そこから作ったキャラは2人。それぞれが別のデータを持っています。この「1枚の設計図から何人でも作れる」感覚が、オブジェクト指向の便利さの中心です。
RPGの例とコードを1行ずつ対応させる
たとえ話とコードを、ここで完全に結びつけます。上から順に読んでください。
| RPGでの意味 | 実際のコード | やっていること |
|---|---|---|
| 勇者という職業の設計図 | class Hero: | クラスの定義 |
| キャラ作成時の初期ステータス | def init(self, name, hp): | 初期設定の準備 |
| 名前という能力を持たせる | self.name = name | プロパティの設定 |
| HPという能力を持たせる | self.hp = hp | プロパティの設定 |
| 「攻撃する」という行動を覚える | def attack(self): | メソッドの定義 |
| 勇者タロウが誕生する | hero = Hero("タロウ", 100) | インスタンスの生成 |
| タロウが攻撃コマンドを使う | hero.attack() | メソッドの呼び出し |
| タロウのHPを確認する | hero.hp | プロパティの参照 |
ドット(.)は「〜の」と読み替えると一気に読みやすくなります。hero.attack() は「heroの攻撃を実行」。hero.hp は「heroのHP」。それだけです。
この対応表を見ながら、上のコードを自分の手で打ち込んでみてください。読むのと打つのでは、定着がまるで違います。
関連記事:APIとは?未経験向けに仕組み・使い方・実装例まで解説
Section 04オブジェクト指向の三大要素をわかりやすく解説
ここからが本題です。ただし身構える必要はありません。三大要素はすべて、RPGの延長で説明できます。
カプセル化|間違った操作を防ぐ仕組み
カプセル化とは、外から勝手にいじられたくないデータを隠す仕組みです。
RPGで考えます。HPを外部から自由に書き換えられたら、どうなるでしょうか。マイナス9999という値も設定できてしまいます。バグの温床です。
そこで「HPは直接触らせない。ダメージを受けるメソッド経由でしか変えられない」とルールを決めます。
Python
class Hero:
def init(self, name, hp):
self.name = name
self.__hp = hp # 先頭に__を付けると外から触りにくくなる
def damage(self, value):
self.__hp -= value
if self.__hp < 0: # マイナスにならないよう見張る
self.__hp = 0
def show_hp(self):
print(self.name + "のHP:", self.__hp)
hero = Hero("タロウ", 100)
hero.damage(30)
hero.show_hp()
hero.damage(500)
hero.show_hp()タロウのHP: 70
タロウのHP: 0500のダメージを受けても、HPは0で止まります。安全装置をクラスの内側に持たせたわけです。
実務でも効きます。私が担当した案件では、金額を扱うクラスで同じ考え方を使いました。負の金額が入らないよう、値の変更をメソッド1本に絞る。これだけで、想定外の値によるバグが目に見えて減りました。
継承|親キャラクターの能力を引き継ぐ
継承とは、既存のクラスの機能を引き継いで新しいクラスを作る仕組みです。
勇者も魔法使いも、名前を持ち、攻撃できます。共通部分を毎回書き直すのは無駄です。そこで「キャラクター」という親を作り、そこから各職業を派生させます。
Python
class Character: # 親クラス(共通の土台)
def init(self, name):
self.name = name
def attack(self):
print(self.name + "の攻撃!")
class Hero(Character): # Characterを引き継ぐ
def heal(self):
print(self.name + "は回復した!")
hero = Hero("タロウ")
hero.attack() # 親から引き継いだメソッド
hero.heal() # 勇者だけの独自メソッドタロウの攻撃!
タロウは回復した!Hero の中に attack は書いていません。それでも動きます。親から受け継いでいるからです。
注意点もあります。継承を深く重ねると、処理がどこに書いてあるか追えなくなります。実務では「3階層以上は要注意」と言われることが多いです。便利ですが、使いすぎない。この距離感が大事です。
ポリモーフィズム|同じ命令でも動きが変わる
ポリモーフィズムは、三大要素で一番とっつきにくい言葉です。意味は「同じ命令を出しても、相手によって結果が変わる」だけです。
パーティ全員に「攻撃!」と号令をかける場面を想像してください。勇者は剣で斬る。魔法使いは炎を撃つ。命令はひとつ、動きは別々。これがポリモーフィズムです。
Python
class Hero:
def attack(self):
print("剣で斬りつけた!")
class Wizard:
def attack(self):
print("ファイアボールを唱えた!")
party = [Hero(), Wizard()]
for member in party:
member.attack() # 命令は全員同じ剣で斬りつけた!
ファイアボールを唱えた!呼び出す側は、相手が勇者か魔法使いかを気にしていません。attack() と言うだけ。新しい職業を追加しても、この呼び出し側は1行も変えずに済みます。
ここが実務で最も効く場面です。支払い方法をクレジットカード・銀行振込・コンビニ払いと増やしたとき、私は呼び出し側をほとんど触りませんでした。新しいクラスを1つ足すだけで済んだ経験は、地味に感動があります。
三大要素は、暗記ではなくこの3つの場面を思い出せれば十分です。
Section 05オブジェクト指向を学ぶメリット・デメリット
良い面だけを並べても判断できません。両方を並べます。
メリット① 修正しやすい
変更が必要になったとき、直す場所が1か所に決まります。HPの計算ルールを変えたいなら、該当クラスの中だけを見ればいい。
手続き型だと、同じ計算が複数の関数にコピーされていることがあります。1つ直して安心していたら、別の場所が古いまま。この事故が起きにくくなります。
メリット② チーム開発に向いている
クラス単位で作業を分担できます。「あなたはユーザー周り、私は決済周り」と切り分けやすい。
実務での実感として、これが一番大きいです。担当が分かれていれば、同じファイルを同時に触る衝突も減ります。レビューのときも、見るべき範囲が明確になります。
メリット③ 再利用しやすい
一度作ったクラスは、別の画面でも別の機能でも使い回せます。設計図が1枚あれば、そこから何人でもキャラを作れるのと同じです。
PHPの実務でも、同じ考え方が日常的に使われています。たとえばユーザーを表すクラスは、こんな形です。
PHP
class User
{
public function __construct(
private string $name,
private int $age
) {}
public function isAdult(): bool
{
return $this->age >= 18;
}
}
$user = new User("タロウ", 20);
var_dump($user->isAdult()); // bool(true)言語が変わっても、設計図とインスタンスという考え方は同じです。一度理解すれば、他の言語にそのまま持ち込めます。
デメリット① 最初は難しい
正直に書きます。理解までに時間がかかります。私も腹落ちまで数か月かかりました。
理由は、書ける量が増えないと必要性を感じないからです。50行のプログラムでは、クラスを使う恩恵がほぼありません。「便利さが分かる前に、面倒くささだけ味わう」段階があります。
デメリット② 小規模開発では不要な場合もある
数十行のスクリプトなら、手続き型のほうが速く書けます。クラスを作るぶん、記述量は増えます。
設計を凝りすぎて、かえって読みにくくなるケースも見てきました。目的は綺麗な設計ではなく、動いて直しやすいコードです。
| 内容 | 効いてくる場面 | |
|---|---|---|
| メリット | 修正しやすい/分担しやすい/再利用できる | 長く運用するサービス |
| デメリット | 習得に時間がかかる/記述量が増える | 使い捨ての小さな処理 |
今すぐ完璧に使いこなす必要はありません。「大きくなったら効く道具」と割り切って学び始めてください。
Section 06初心者がオブジェクト指向を学ぶおすすめの方法
ここからは、明日から動ける形に落とします。
おすすめ言語はPython・Java・Ruby
最初の1本は、この3つのどれかが学びやすいです。
- Python:記号が少なく、クラスの構造が読みやすい。最初の1本に向く
- Java:クラスを書かないと動かない。強制的に身につく
- Ruby:ほぼすべてがオブジェクト。日本語情報も豊富
Web制作から入った方なら、PHPやTypeScriptでも問題ありません。私はPHPで覚えました。言語より、手を動かした回数のほうが効きます。
関連記事:PHPの将来性と需要は?未経験転職を現役が解説/TypeScript入門|未経験から学ぶロードマップ
最初から完璧に理解しようとしない
三大要素をすべて理解してから書き始める。この順番だと、たいてい挫折します。
おすすめは逆です。まずクラスとインスタンスだけで動くものを作る。継承やポリモーフィズムは、「同じコードを2回書いていて面倒だな」と感じてからで間に合います。必要性を感じた瞬間が、一番覚えられるタイミングです。
実際にクラスを書いて覚える
最後に、20行ちょっとの練習課題を置きます。この記事のRPGの続きです。コピペして動かしてみてください。
Python
class Character:
def init(self, name, hp, power):
self.name = name
self.hp = hp
self.power = power
def attack(self, target):
target.hp -= self.power
print(self.name + "は" + target.name + "に" + str(self.power) + "のダメージ!")
def is_alive(self):
return self.hp > 0
hero = Character("勇者タロウ", 100, 20)
slime = Character("スライム", 50, 5)
while hero.is_alive() and slime.is_alive():
hero.attack(slime)
if not slime.is_alive():
print(slime.name + "を倒した!")
break
slime.attack(hero)勇者タロウはスライムに20のダメージ!
スライムは勇者タロウに5のダメージ!
勇者タロウはスライムに20のダメージ!
スライムは勇者タロウに5のダメージ!
勇者タロウはスライムに20のダメージ!
スライムを倒した!動いたら、次は自分で改造します。魔法使いクラスを継承で追加する。HPをカプセル化する。回復メソッドを足す。改造した瞬間に、三大要素が自分の道具になります。
Section 07オブジェクト指向でよくある質問
検索でよく見かける疑問に、短く答えます。
オブジェクト指向の三大要素とは?
カプセル化・継承・ポリモーフィズムの3つです。順に「データを守る」「機能を引き継ぐ」「同じ命令で違う動きをする」と覚えてください。抽象化を加えて四大要素とする説明もあります。数え方の違いなので、混乱しなくて大丈夫です。
クラスとインスタンスの違いは?
クラスは設計図、インスタンスはそこから作った実体です。クラスは1枚、インスタンスは何個でも作れます。タロウがダメージを受けてもハナコのHPが減らないのは、両者が別のインスタンスだからです。
オブジェクト指向ではない言語はありますか?
あります。C言語は代表例で、クラスの仕組みを標準では持ちません。また関数を中心に組み立てる「関数型」という考え方の言語もあります。オブジェクト指向が唯一の正解ではありません。用途に応じて選ばれる道具のひとつです。
初心者におすすめの言語は?
読みやすさを優先するならPythonです。Web制作の経験があるなら、PHPやJavaScriptから入っても構いません。すでに学習中の言語があるなら、それを続けるのが最短です。乗り換えのコストのほうが高くつきます。
なぜオブジェクト指向は難しいと言われるのですか?
理由は2つあると考えています。1つは、たとえ話とコードの間に橋がかかっていない解説が多いこと。もう1つは、小さなプログラムでは必要性を実感できないことです。
逆に言えば、コードとの対応を確認しながら、ある程度の規模を書けば必ず分かります。才能の問題ではありません。
Section 08まとめ|まずはクラスとインスタンスを書いてみよう
長い記事でしたが、持ち帰ってほしいことは多くありません。
今日覚えてほしいポイント
- オブジェクト指向は「モノ」単位で考えるプログラミング
- クラスは設計図、インスタンスはそこから作った実体
- プロパティはデータ、メソッドは行動
- 三大要素はカプセル化・継承・ポリモーフィズム
- 効いてくるのは、長く運用する中〜大規模の開発
この5つが言葉で説明できれば、入り口は突破しています。
次に学ぶべき内容
- この記事の最後のコードを写経して動かす
- 魔法使いクラスを継承で追加してみる
- HPをカプセル化して、マイナスにならないようにする
- フレームワーク(LaravelやDjangoなど)の中で、実際のクラスを読む
4番まで進めば、実務のコードが読める景色に変わります。私がそうでした。
まずは1番だけで構いません。エディタを開いて、今日中に1つクラスを書いてみてください。理解は、そのあとから追いついてきます。
