エンジアップ エンジアップ

もう迷わない。ITエンジニアのための総合情報サイト

オブジェクト指向とは何か?初心者向けに4つの柱をやさしく・具体的に解説
投稿
X LINE B! f

オブジェクト指向とは何か?初心者向けに4つの柱をやさしく・具体的に解説

プログラミングを「もの」の視点で考える

「オブジェクト指向」という言葉は、プログラミングを学び始めた頃に最初の壁として立ちはだかることがあります。クラス、インスタンス、継承、ポリモーフィズム……と、聞き慣れない言葉が次々と出てきて、「自分には難しすぎるのでは」と感じた経験がある方も多いのではないでしょうか。

実は、オブジェクト指向の根っこにある考え方はとてもシンプルです。「プログラムを現実世界の"もの"に例えて組み立てる」——これだけです。人間は現実の世界を「もの(オブジェクト)」の集まりとして捉えています。車・犬・ユーザー・注文……これらを「設計図」として定義し、そこから実体を作って動かすのがオブジェクト指向の発想です。

この記事では、できるだけ身近な例を使いながら、オブジェクト指向の4つの柱を丁寧に解説します。PythonとJavaの簡単なコードも使いますが、「コードを書けるようになること」より「考え方を掴むこと」を優先して読み進めてください。

クラスとインスタンス:設計図と実体

オブジェクト指向を理解するうえで最初に押さえるべき概念がクラス(Class)インスタンス(Instance)です。

クラスとは「設計図」です。「犬」というクラスを作るとき、犬が持つべき情報(名前、犬種、年齢)と、犬ができること(鳴く、走る)を定義します。インスタンスとは、その設計図から作られた「実体」です。「ポチ」や「モモ」は「犬」という設計図から作られた個別の存在です。

class Dog:
    def __init__(self, name, breed):
        self.name = name    # 属性:名前
        self.breed = breed  # 属性:犬種

    def bark(self):         # メソッド:鳴く
        print(f"{self.name}:ワンワン!")

# インスタンスを生成する
pochi = Dog("ポチ", "柴犬")
momo  = Dog("モモ", "トイプードル")

pochi.bark()  # ポチ:ワンワン!
momo.bark()   # モモ:ワンワン!

Dogクラスという設計図を一度作っておけば、そこから何匹でも犬を作れます。そして各インスタンスは自分固有の名前と犬種を持ちながら、同じ「鳴く」という振る舞いを共有しています。これがオブジェクト指向の基本的な仕組みです。

クラスが持つ情報をフィールド(属性・プロパティ)、クラスが持つ機能をメソッドと呼びます。データと処理をひとまとめにして管理するこの考え方が、コードを整理するうえで非常に強力です。

4つの柱①:カプセル化——「必要なものだけ見せる」

カプセル化(Encapsulation)とは、オブジェクトの内部の詳細を隠蔽し、外から必要なインターフェースだけを公開する考え方です。テレビのリモコンを思い浮かべてください。ボタンを押すと動きますが、中の電気回路の仕組みを知らなくても使えます。複雑な内部実装を隠して、シンプルな操作窓口だけを提供している——これがカプセル化の本質です。

class BankAccount:
    def __init__(self, owner):
        self.owner = owner
        self.__balance = 0  # __をつけるとクラス外から直接触れない(プライベート)

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount

    def get_balance(self):
        return self.__balance  # 残高の取得は専用メソッド経由

account = BankAccount("田中さん")
account.deposit(10000)
print(account.get_balance())   # 10000

# account.__balance = -99999  # これはできない(エラーになる)

__balance(残高)を直接書き換えられないようにすることで、「マイナス残高への不正な書き換え」を防いでいます。内部の実装を隠すことで、「使う側」は細かいことを気にせず安全にオブジェクトを扱えます。

4つの柱②:継承——「共通部分を親から受け取る」

継承(Inheritance)とは、あるクラスの機能や属性を別のクラスが引き継ぐ仕組みです。「動物」という共通の特徴を持つ「犬」「猫」「鳥」をそれぞれ独立して作ると、「名前を持つ」「食べる」などの共通部分をコードに何度も書かなければなりません。

class Animal:           # 親クラス(スーパークラス)
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(f"{self.name}が食事をしています")

class Dog(Animal):      # Animalを継承した子クラス
    def bark(self):
        print(f"{self.name}:ワンワン!")

class Cat(Animal):      # Animalを継承した子クラス
    def meow(self):
        print(f"{self.name}:ニャー!")

dog = Dog("ポチ")
dog.eat()    # Animalのメソッドが使える → ポチが食事をしています
dog.bark()   # Dogのメソッド → ポチ:ワンワン!

「動物なら食べる」という共通の振る舞いはAnimal親クラスに書き、「犬だから鳴き方が違う」という固有の振る舞いはDog子クラスに書く——こうすることでコードの重複を減らし、変更箇所を最小化できます。実務での具体例として、Webフレームワークの「モデル基底クラス」や「コントローラー基底クラス」がまさにこの継承を活用しています。

4つの柱③:ポリモーフィズム——「同じ命令で、それぞれの動き」

ポリモーフィズム(Polymorphism)は日本語で「多態性」と訳されます。難しそうな言葉ですが、要は「同じメソッド名で呼ぶのに、オブジェクトの種類によって動作が変わる」ということです。

class Dog(Animal):
    def speak(self):
        print(f"{self.name}:ワンワン!")

class Cat(Animal):
    def speak(self):       # 同じメソッド名 "speak" だが動作が違う
        print(f"{self.name}:ニャー!")

class Bird(Animal):
    def speak(self):
        print(f"{self.name}:チュンチュン!")

animals = [Dog("ポチ"), Cat("モモ"), Bird("ピー")]

for animal in animals:
    animal.speak()   # 呼び出す側は動物の種類を気にしなくていい
# ポチ:ワンワン!
# モモ:ニャー!
# ピー:チュンチュン!

animal.speak()という同じ命令を送るだけで、それぞれが適切な鳴き方をしてくれます。呼び出す側は「このオブジェクトが犬か猫かを判定してif文で分岐する」必要がありません。新しい動物(たとえばウサギ)を追加するときも、既存のコードに一切手を入れずにspeakを定義するだけで対応できます。これが開放/閉鎖原則(Open/Closed Principle)の実践でもあり、拡張しやすく変更に強いコードの基盤になります。

4つの柱④:抽象化——「共通の約束事を決める」

抽象化(Abstraction)とは、複数のクラスに共通する「インターフェース(約束事)」を定義する考え方です。「この仕組みを使いたければ、必ずこのメソッドを実装してください」という契約書のようなものです。

Pythonでは抽象基底クラス(ABC)を使って表現します。

from abc import ABC, abstractmethod

class Shape(ABC):           # 抽象クラス(直接インスタンス化できない)
    @abstractmethod
    def area(self) -> float:    # 必ず実装しなければならないメソッド
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    def area(self) -> float:
        return 3.14159 * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    def area(self) -> float:
        return self.width * self.height

shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
    print(f"面積: {shape.area():.2f}")

area()メソッドの実装を強制することで、「面積を返すarea()があること」を保証できます。あとから三角形を追加しても、area()さえ実装すれば他のコードはそのまま動き続けます。

4つの柱まとめ

概念一言で言うと現実の例え
カプセル化内部を隠してシンプルな窓口だけ公開リモコン(回路は隠れている)
継承共通部分を親から引き継ぐ生物→哺乳類→犬の分類
ポリモーフィズム同じ命令でも種類ごとに動作が変わる「話す」でも犬は吠え、猫は鳴く
抽象化必要なメソッドの契約だけ定義する「支払い可能なものは決済できる」

よくある誤解:「継承を多用するのが正しい」は古い考え

オブジェクト指向を学ぶと継承を多用したくなりますが、現代のソフトウェア設計では「継承より合成(Composition over Inheritance)」が推奨されています。深い継承ツリーは変更の影響範囲が予測しにくくなり、「親クラスを変えたら子クラスが壊れた」という問題(脆弱な基底クラス問題)を引き起こします。

「犬は動物を継承する」のようなis-a関係(〜は〜の一種である)には継承が適切ですが、「犬はしっぽを持つ」のようなhas-a関係(〜は〜を持つ)には継承ではなくオブジェクトをフィールドとして持つ「合成」を使う方が柔軟です。

まとめ

オブジェクト指向は「コードを現実世界のものに例えて整理する思考法」です。クラスという設計図からインスタンスを作り、カプセル化・継承・ポリモーフィズム・抽象化の4つの考え方で、読みやすく変更に強いコードを組み立てます。最初は難しく感じても、手を動かして自分でクラスを書いてみると、不思議と「なるほど」となる瞬間が来ます。まずは自分の身の周りの「もの」をクラスとして表現する練習から始めてみてください。