Labiteお問い合わせ
プログラミング公開日

オブジェクト指向をわかりやすく解説

オブジェクト指向をわかりやすく解説

この記事でわかることsummary

  • オブジェクト指向とは何かを初心者向けに理解できる
  • クラス・インスタンス・メソッド・プロパティの違いが分かる
  • オブジェクト指向の三大要素を図解イメージで理解できる
  • なぜ実務でオブジェクト指向が使われるのか分かる
  • 次に学ぶべきプログラミング言語や学習方法が分かる

オブジェクト指向わかりやすく説明した記事が見つからない」。そう思ってここに来た方が多いはずです。

読んでも読んでも、たとえ話が急にコードに変わって置いていかれる。カプセル化、継承、ポリモーフィズム。用語だけが増えていく。

その気持ち、痛いほど分かります。私も独学時代、この壁の前で三か月ほど足踏みしました。

私は普段、PHPとLaravelでWebアプリを開発しています。実務歴は2年と少し。今は毎日クラスを書いていますが、最初はまったく理解できませんでした。この記事ではRPGのキャラクターという例えを最後まで一本で通します。たとえ話とコードを1行ずつ対応させるので、途中で迷子になりません。

オブジェクト指向をわかりやすく言うと「モノ」を中心に考えるプログラミング

細かい定義は後回しにします。まず全体像を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エンジニアを目指す始め方

オブジェクト指向をわかりやすく理解するならRPGのキャラクターで考えよう

ここからは、たとえ話で骨組みを作ります。使うのはRPGのキャラクターです。この例えを記事の最後まで使い続けます。

クラスは「キャラクターの設計図」

クラスとは設計図です。実体ではありません。

RPGでいえば「勇者という職業の仕様書」にあたります。勇者は名前を持つ。HPを持つ。攻撃ができる。そう決めた紙が1枚あるイメージです。その紙自体はゲーム画面に出てきません。

ここが初心者のつまずきポイントです。クラスを書いただけでは何も起きません。設計図を書いただけで家が建たないのと同じです。

インスタンスは「実際に作ったキャラクター」

設計図から実際に生み出したものがインスタンスです。

「勇者」の設計図から、名前がタロウのキャラクターを作る。名前がハナコのキャラクターも作る。この2人がインスタンスです。同じ設計図から作られていますが、中身のデータは別々です。タロウがダメージを受けても、ハナコのHPは減りません。

ゲームのキャラメイク画面を思い出してください。あの画面で「決定」を押した瞬間が、インスタンスの生成です。

プロパティは「レベル・HPなどの能力」

プロパティは、そのキャラクターが持つデータです。名前、HP、攻撃力、レベル。ステータス画面に並んでいる数値がこれにあたります。

属性やフィールドと呼ばれることもあります。呼び方は言語によって変わりますが、指しているものは同じです。「そのモノが持っている情報」と覚えれば困りません。

メソッドは「攻撃・回復などの行動」

メソッドは、そのキャラクターができる行動です。攻撃する、回復する、逃げる。戦闘中のコマンド欄に並ぶものだと考えてください。

関数との違いを聞かれることがあります。ざっくり言えば、クラスの中に書かれた関数がメソッドです。それ以上の区別は、今は気にしなくて大丈夫です。

4つの用語を一枚で整理します。

用語RPGでいうと役割
クラス勇者の設計図仕様を定義する
インスタンス勇者タロウ本人設計図から作られた実体
プロパティHP・攻撃力・レベル持っているデータ
メソッド攻撃する・回復するできる行動

この表を一度スクショしておいてください。以降のコードで迷ったら、ここに戻れば必ず解けます。

RPGの例を実際のコードに置き換えてみよう

ここが多くの人の離脱ポイントです。たとえ話は分かった、でもコードになると分からない。橋をゆっくり架けていきます。

Pythonでクラスを書いてみる

まず設計図を書きます。Pythonを使うのは、記号が少なく読みやすいからです。

Python

class Hero:
    # インスタンスを作るときに呼ばれる初期設定
    def init(self, name, hp):
        self.name = name   # プロパティ:名前
        self.hp = hp       # プロパティ:HP
    # メソッド:攻撃する
    def attack(self):
        print(self.name + "の攻撃!")

見慣れない部分を3つだけ解説します。

  1. class Hero: は「勇者の設計図をここから書きます」という宣言です。
  2. __init__ は、キャラを作る瞬間に自動で動く初期設定です。ステータスの初期値を決める場所だと思ってください。
  3. 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とは?未経験向けに仕組み・使い方・実装例まで解説

オブジェクト指向の三大要素をわかりやすく解説

ここからが本題です。ただし身構える必要はありません。三大要素はすべて、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: 0

500のダメージを受けても、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つの場面を思い出せれば十分です。

オブジェクト指向を学ぶメリット・デメリット

良い面だけを並べても判断できません。両方を並べます。

メリット① 修正しやすい

変更が必要になったとき、直す場所が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行のプログラムでは、クラスを使う恩恵がほぼありません。「便利さが分かる前に、面倒くささだけ味わう」段階があります。

デメリット② 小規模開発では不要な場合もある

数十行のスクリプトなら、手続き型のほうが速く書けます。クラスを作るぶん、記述量は増えます。

設計を凝りすぎて、かえって読みにくくなるケースも見てきました。目的は綺麗な設計ではなく、動いて直しやすいコードです

内容効いてくる場面
メリット修正しやすい/分担しやすい/再利用できる長く運用するサービス
デメリット習得に時間がかかる/記述量が増える使い捨ての小さな処理

今すぐ完璧に使いこなす必要はありません。「大きくなったら効く道具」と割り切って学び始めてください。

初心者がオブジェクト指向を学ぶおすすめの方法

ここからは、明日から動ける形に落とします。

おすすめ言語は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をカプセル化する。回復メソッドを足す。改造した瞬間に、三大要素が自分の道具になります

オブジェクト指向でよくある質問

検索でよく見かける疑問に、短く答えます。

オブジェクト指向の三大要素とは?

カプセル化・継承・ポリモーフィズムの3つです。順に「データを守る」「機能を引き継ぐ」「同じ命令で違う動きをする」と覚えてください。抽象化を加えて四大要素とする説明もあります。数え方の違いなので、混乱しなくて大丈夫です。

クラスとインスタンスの違いは?

クラスは設計図、インスタンスはそこから作った実体です。クラスは1枚、インスタンスは何個でも作れます。タロウがダメージを受けてもハナコのHPが減らないのは、両者が別のインスタンスだからです。

オブジェクト指向ではない言語はありますか?

あります。C言語は代表例で、クラスの仕組みを標準では持ちません。また関数を中心に組み立てる「関数型」という考え方の言語もあります。オブジェクト指向が唯一の正解ではありません。用途に応じて選ばれる道具のひとつです。

初心者におすすめの言語は?

読みやすさを優先するならPythonです。Web制作の経験があるなら、PHPやJavaScriptから入っても構いません。すでに学習中の言語があるなら、それを続けるのが最短です。乗り換えのコストのほうが高くつきます。

なぜオブジェクト指向は難しいと言われるのですか?

理由は2つあると考えています。1つは、たとえ話とコードの間に橋がかかっていない解説が多いこと。もう1つは、小さなプログラムでは必要性を実感できないことです。

逆に言えば、コードとの対応を確認しながら、ある程度の規模を書けば必ず分かります。才能の問題ではありません。

まとめ|まずはクラスとインスタンスを書いてみよう

長い記事でしたが、持ち帰ってほしいことは多くありません。

今日覚えてほしいポイント

  • オブジェクト指向は「モノ」単位で考えるプログラミング
  • クラスは設計図、インスタンスはそこから作った実体
  • プロパティはデータ、メソッドは行動
  • 三大要素はカプセル化・継承・ポリモーフィズム
  • 効いてくるのは、長く運用する中〜大規模の開発

この5つが言葉で説明できれば、入り口は突破しています。

次に学ぶべき内容

  1. この記事の最後のコードを写経して動かす
  2. 魔法使いクラスを継承で追加してみる
  3. HPをカプセル化して、マイナスにならないようにする
  4. フレームワーク(LaravelやDjangoなど)の中で、実際のクラスを読む

4番まで進めば、実務のコードが読める景色に変わります。私がそうでした。

まずは1番だけで構いません。エディタを開いて、今日中に1つクラスを書いてみてください。理解は、そのあとから追いついてきます。

関連記事:未経験からWebエンジニアを目指すロードマップ

Labite運営者 しゅう

しゅう

Webエンジニア | Labite運営者

未経験からWebエンジニアになり、PHP・Laravel・JavaScriptを用いた実務経験をもとに、学習・開発・キャリア情報を発信しています。

プロフィールを見る
ゴール
0%
読書の進み具合