16.

オブジェクト指向プログラミング (OOP) 完全ガイド

編集
この記事の要点
  • OOP (Object-Oriented Programming) はデータ (属性) とふるまい (メソッド) を クラス にまとめ、オブジェクトを介して相互作用させる設計手法
  • 三原則: カプセル化 (encapsulation) / 継承 (inheritance) / ポリモーフィズム (polymorphism)。これに抽象化を加えた四要素で語られることも
  • SOLID 原則: 単一責任 / 開放閉鎖 / リスコフ置換 / インターフェース分離 / 依存性逆転
  • 主要 OOP 言語: Java / C# / Python / PHP / Ruby / TypeScript / Kotlin / Swift
  • 現代のトレンド: 継承より合成 (composition over inheritance) / record / readonly による不変オブジェクト / 関数型との融合

オブジェクト指向プログラミングとは

OOP は、データ(属性)とそれを操作する処理(メソッド)をクラスという単位にまとめ、その実体(インスタンス/オブジェクト)どうしを協調させて動かす設計手法です。1960 年代の Simula 67、1970 年代の Smalltalk から始まり、C++ / Java / C# / Python など現在主流の言語の大半が OOP の特徴を備えています。

OOP の三原則

原則意味実装
カプセル化内部状態を隠蔽し、メソッド経由でのみアクセスprivate / protected 修飾子、getter/setter
継承親クラスの属性・メソッドを子クラスに引き継ぐextends / : / inherit
ポリモーフィズム同じインターフェースで複数の型を扱えるinterface / override / 仮想関数
抽象化(4 要素説)本質だけを取り出し詳細を隠すabstract class / interface

カプセル化の例

// Java
public class BankAccount {
    private long balance;  // ★ private で外から触れない

    public void deposit(long amount) {
        if (amount <= 0) throw new IllegalArgumentException();
        balance += amount;
    }

    public long getBalance() { return balance; }
}

// 利用側
BankAccount acc = new BankAccount();
acc.deposit(1000);          // OK
// acc.balance = -999999;   // コンパイルエラー

継承とポリモーフィズム

// C#
public abstract class Animal
{
    public string Name { get; init; }
    public abstract string Cry();
}

public class Dog : Animal
{
    public override string Cry() => "ワン";
}

public class Cat : Animal
{
    public override string Cry() => "ニャー";
}

// 多態性
Animal[] zoo = { new Dog { Name = "Pochi" }, new Cat { Name = "Tama" } };
foreach (var a in zoo)
    Console.WriteLine($"{a.Name}: {a.Cry()}");
// Pochi: ワン
// Tama: ニャー

interface と abstract class

項目interfaceabstract class
状態保持不可(一部言語で default 実装可)可能
多重実装可能不可(単一継承)
用途「できること」の契約共通の基底実装
Comparable, Serializable, IteratorShape (base), HttpServlet

SOLID 原則

頭文字原則要約
SSingle Responsibility1 クラス 1 責務。変更理由は 1 つだけ
OOpen / Closed拡張に開き、修正に閉じる(既存コードを変えず追加で対応)
LLiskov Substitution派生クラスは基底クラスと置換可能であるべき
IInterface Segregation巨大インターフェースより小さく分割
DDependency Inversion具象でなく抽象(interface)に依存する

代表的なデザインパターン

パターン分類目的
Singleton生成クラスのインスタンスを 1 つに制限
Factory Method生成生成ロジックをサブクラスへ委譲
Builder生成複雑なオブジェクトを段階的に構築
Adapter構造既存インターフェースを別の I/F に適合
Decorator構造振舞いを動的に追加
Observer振舞い状態変化を購読者に通知(Pub/Sub)
Strategy振舞いアルゴリズムを差し替え可能に
Template Method振舞い骨組みを基底、詳細をサブクラスで
State振舞い状態ごとに振舞いを切替

言語別の書き方比較

// Java: クラス定義
public class User {
    private final String name;
    private int age;

    public User(String name, int age) {
        this.name = name; this.age = age;
    }
    public String getName() { return name; }
    public int getAge()  { return age; }
}

// Java 16+ : record(イミュータブル値オブジェクト)
public record UserRecord(String name, int age) {}
// C#: クラス + プロパティ
public class User
{
    public string Name { get; }
    public int Age { get; set; }
    public User(string name, int age) => (Name, Age) = (name, age);
}

// C# 9+ : record
public record User(string Name, int Age);
# Python
from dataclasses import dataclass

class User:
    def __init__(self, name: str, age: int):
        self._name = name
        self.age = age

    @property
    def name(self) -> str:
        return self._name

# dataclass(PEP 557, Python 3.7+)
@dataclass
class UserDC:
    name: str
    age: int
name;        // OK
// $u->name = 'X';    // Error: cannot modify readonly property
$u->age = 31;         // OK

継承より合成 (Composition over Inheritance)

継承は強力ですが、深い継承ツリーは保守困難になります。has-a (持っている) 関係には継承ではなく合成を使うのが現代の定石:

// ❌ 継承で無理に再利用
public class Bird : Animal { public void Fly() {...} }
public class Penguin : Bird {}   // ペンギンは飛べない → Liskov 違反

// ✅ 合成
public interface IFlyBehavior { void Fly(); }
public class WingFly : IFlyBehavior { public void Fly() { ... } }
public class NoFly  : IFlyBehavior { public void Fly() { /* 何もしない */ } }

public class Bird
{
    private readonly IFlyBehavior _flyer;
    public Bird(IFlyBehavior flyer) => _flyer = flyer;
    public void Fly() => _flyer.Fly();
}

var sparrow = new Bird(new WingFly());
var penguin = new Bird(new NoFly());

関数型との組み合わせ

現代の OOP 言語は関数型機能(ラムダ・高階関数・不変データ)を取り込み、ハイブリッド化しています:

// Java Stream API + record
record Order(String customer, int amount) {}

List orders = List.of(
    new Order("A", 100), new Order("B", 200), new Order("A", 50)
);

Map total = orders.stream()
    .collect(Collectors.groupingBy(
        Order::customer,
        Collectors.summingInt(Order::amount)
    ));
// {A=150, B=200}

FAQ

Q: 関数型と OOP、どちらを学ぶべき?
A: どちらも。現代の主要言語はハイブリッド。OOP で構造化、関数型で副作用を抑える、を両立できると強い。

Q: 継承はもう古い?
A: 完全に古いわけではないが、「is-a 関係」が明確で挙動を上書きする必要があるときだけ使う。それ以外は interface + 合成。

Q: SOLID は丸暗記すべき?
A: 丸暗記よりレビューでパターン認識できるレベルまで体感する。最初は S と D だけ意識すれば十分。

Q: record / readonly は本当に使うべき?
A: 値オブジェクト (DTO, Event, ID) には積極的に。可変オブジェクトはバグの温床になりがち。

編集
Post Share
子ページ
  1. オブジェクト指向の概念
  2. 継承の概念と必要性
  3. ポリモーフィズム(多様性)の概念と必要性
  4. 抽象クラスの概念と必要性
  5. インターフェースの概念と必要性
  6. カプセル化の概念と必要性
同階層のページ
  1. Java
  2. PHP
  3. Python
  4. C#
  5. C++
  6. Ruby
  7. Go
  8. HTML
  9. CSS
  10. JavaScript
  11. TypeScript
  12. VBA
  13. Google Apps Script
  14. Julia
  15. Swift
  16. オブジェクト指向言語共通
  17. Gen