Skilore

OOPとは何か

オブジェクト指向プログラミング(OOP)は「データとそれを操作する手続きを一つの単位(オブジェクト)にまとめる」プログラミングパラダイム。現実世界のモデリングから大規模ソフトウェアの構造化まで、最も広く使われている設計手法。

112 分で読めます55,725 文字

OOPとは何か

オブジェクト指向プログラミング(OOP)は「データとそれを操作する手続きを一つの単位(オブジェクト)にまとめる」プログラミングパラダイム。現実世界のモデリングから大規模ソフトウェアの構造化まで、最も広く使われている設計手法。

この章で学ぶこと

  • OOPの本質的な考え方を理解する
  • オブジェクトとメッセージパッシングの関係を把握する
  • OOPが解決する問題と適用領域を理解する
  • OOPの基本原則が実際のコードにどう反映されるかを体験する
  • 複数言語でのOOP実装の違いを比較する

前提知識

このガイドを読む前に、以下の知識があると理解が深まります:

  • 基本的なプログラミングの知識
  • 関連する基礎概念の理解

1. OOPの本質

プログラミングパラダイムの比較:

  手続き型:    データ + 関数(別々に管理)
  OOP:        データ + 関数 = オブジェクト(一体化)
  関数型:     関数(データ変換のパイプライン)

OOPの核心:
  「世界をオブジェクトの集まりとして捉え、
   オブジェクト間のメッセージのやり取りで処理を進める」

Alan Kayの定義(Smalltalk の設計者):
  1. Everything is an object(すべてはオブジェクト)
  2. Objects communicate by sending messages(メッセージで通信)
  3. Objects have their own memory(独自のメモリを持つ)
  4. Every object is an instance of a class(クラスのインスタンス)
  5. The class holds shared behavior(クラスが共通の振る舞いを保持)

1.1 OOPの定義を深掘りする

OOPの定義は時代と論者によって異なる。大きく分けて2つの流派がある。

Scandinavian School(スカンジナビア学派):
  → Simula から派生
  → クラス、継承、静的型付けを重視
  → C++, Java, C# に受け継がれる
  → 「OOP = クラスベースのプログラミング」

American School(アメリカ学派):
  → Smalltalk から派生
  → メッセージパッシング、動的型付けを重視
  → Ruby, Python, Objective-C に受け継がれる
  → 「OOP = オブジェクト間のメッセージング」

現代の統合的理解:
  → どちらか一方ではなく、両方の要素を組み合わせる
  → TypeScript, Kotlin, Swift は両方の長所を取り入れている

1.2 OOPを支える4つの柱

OOPには4つの基本原則がある。これらは後続の章で詳しく扱うが、ここで概要を押さえておく。

4つの柱(Four Pillars of OOP):

  1. カプセル化(Encapsulation)
     → データと振る舞いを1つの単位にまとめる
     → 内部実装を隠蔽し、公開APIのみを提供する
     → 変更の影響範囲を限定する

  2. 継承(Inheritance)
     → 既存クラスの機能を引き継いで新しいクラスを作る
     → コードの再利用を促進する
     → is-a 関係を表現する

  3. ポリモーフィズム(Polymorphism)
     → 同じインターフェースで異なる実装を呼び出す
     → 実行時に適切なメソッドが選択される
     → 柔軟で拡張可能なコードを実現する

  4. 抽象化(Abstraction)
     → 複雑な詳細を隠し、本質的な特徴のみを表現する
     → 抽象クラスやインターフェースで契約を定義する
     → 利用者が知る必要のない複雑さを隠す
// TypeScript: 4つの柱を1つの例で示す
 
// 抽象化: 共通のインターフェースを定義
interface Shape {
  area(): number;
  perimeter(): number;
  describe(): string;
}
 
// カプセル化: 内部データを隠蔽
class Circle implements Shape {
  private readonly _radius: number;
 
  constructor(radius: number) {
    if (radius <= 0) throw new Error("半径は正の数である必要があります");
    this._radius = radius;
  }
 
  // ポリモーフィズム: Shape インターフェースの実装
  area(): number {
    return Math.PI * this._radius ** 2;
  }
 
  perimeter(): number {
    return 2 * Math.PI * this._radius;
  }
 
  describe(): string {
    return `円(半径: ${this._radius})`;
  }
 
  get radius(): number {
    return this._radius;
  }
}
 
// 継承 + ポリモーフィズム
class Rectangle implements Shape {
  constructor(
    private readonly width: number,
    private readonly height: number,
  ) {
    if (width <= 0 || height <= 0) {
      throw new Error("幅と高さは正の数である必要があります");
    }
  }
 
  area(): number {
    return this.width * this.height;
  }
 
  perimeter(): number {
    return 2 * (this.width + this.height);
  }
 
  describe(): string {
    return `長方形(幅: ${this.width}, 高さ: ${this.height})`;
  }
}
 
// 継承: Rectangle を拡張
class Square extends Rectangle {
  constructor(side: number) {
    super(side, side);
  }
 
  describe(): string {
    return `正方形(辺: ${this.perimeter() / 4})`;
  }
}
 
// ポリモーフィズム: 異なる型を同じインターフェースで扱う
function printShapeInfo(shapes: Shape[]): void {
  for (const shape of shapes) {
    console.log(`${shape.describe()} - 面積: ${shape.area().toFixed(2)}`);
  }
}
 
const shapes: Shape[] = [
  new Circle(5),
  new Rectangle(4, 6),
  new Square(3),
];
printShapeInfo(shapes);

2. メンタルモデル

手続き型のメンタルモデル:
  「手順書」— 上から順に実行する命令の列

  1. ユーザー情報を取得する
  2. バリデーションする
  3. データベースに保存する
  4. メールを送信する

OOPのメンタルモデル:
  「役割を持つ人々の組織」— 各人が責任を持って仕事する
User───→Validator───→Database
(データ)(検証)(保存)
│                              │
       │         ┌──────────┐         │
       └────────→│ Mailer   │←────────┘
                 │ (通知)   │
                 └──────────┘

  各オブジェクトは:
    - 自分のデータ(状態)を管理
    - 自分の責任範囲の処理を実行
    - 他のオブジェクトにメッセージ(メソッド呼び出し)を送る

2.1 手続き型からOOPへの思考の転換

手続き型とOOPでは、問題に対するアプローチが根本的に異なる。以下に具体例で比較する。

# === 手続き型アプローチ: ユーザー登録処理 ===
 
# データは辞書で管理
users_db = []
 
def validate_email(email: str) -> bool:
    """メールアドレスの妥当性チェック"""
    return "@" in email and "." in email.split("@")[1]
 
def validate_password(password: str) -> bool:
    """パスワードの妥当性チェック"""
    return len(password) >= 8
 
def hash_password(password: str) -> str:
    """パスワードのハッシュ化"""
    import hashlib
    return hashlib.sha256(password.encode()).hexdigest()
 
def register_user(name: str, email: str, password: str) -> dict:
    """ユーザー登録の手順を逐次実行"""
    # 手順1: バリデーション
    if not validate_email(email):
        raise ValueError("無効なメールアドレス")
    if not validate_password(password):
        raise ValueError("パスワードは8文字以上")
 
    # 手順2: 重複チェック
    for user in users_db:
        if user["email"] == email:
            raise ValueError("既に登録済みのメールアドレス")
 
    # 手順3: ユーザー作成
    user = {
        "name": name,
        "email": email,
        "password_hash": hash_password(password),
    }
 
    # 手順4: 保存
    users_db.append(user)
    return user
 
# 問題点:
# - データ(users_db)と関数が分離している
# - グローバル状態に依存
# - テストが困難(users_db のリセットが必要)
# - 機能追加時に関数がどんどん増える
# === OOPアプローチ: ユーザー登録処理 ===
 
import hashlib
from dataclasses import dataclass, field
from typing import Optional
 
 
class EmailAddress:
    """メールアドレス値オブジェクト: バリデーションを内包"""
    def __init__(self, value: str):
        if "@" not in value or "." not in value.split("@")[1]:
            raise ValueError(f"無効なメールアドレス: {value}")
        self._value = value
 
    @property
    def value(self) -> str:
        return self._value
 
    def __eq__(self, other: object) -> bool:
        if isinstance(other, EmailAddress):
            return self._value == other._value
        return False
 
    def __hash__(self) -> int:
        return hash(self._value)
 
    def __str__(self) -> str:
        return self._value
 
 
class Password:
    """パスワード値オブジェクト: ハッシュ化を内包"""
    def __init__(self, plain_text: str):
        if len(plain_text) < 8:
            raise ValueError("パスワードは8文字以上必要です")
        self._hash = hashlib.sha256(plain_text.encode()).hexdigest()
 
    @property
    def hashed(self) -> str:
        return self._hash
 
    def verify(self, plain_text: str) -> bool:
        return hashlib.sha256(plain_text.encode()).hexdigest() == self._hash
 
 
class User:
    """ユーザーエンティティ: データと振る舞いを統合"""
    def __init__(self, name: str, email: EmailAddress, password: Password):
        self._name = name
        self._email = email
        self._password = password
 
    @property
    def name(self) -> str:
        return self._name
 
    @property
    def email(self) -> EmailAddress:
        return self._email
 
    def authenticate(self, plain_password: str) -> bool:
        """認証はユーザー自身の責任"""
        return self._password.verify(plain_password)
 
 
class UserRepository:
    """ユーザーリポジトリ: データの永続化を担当"""
    def __init__(self):
        self._users: list[User] = []
 
    def find_by_email(self, email: EmailAddress) -> Optional[User]:
        for user in self._users:
            if user.email == email:
                return user
        return None
 
    def save(self, user: User) -> None:
        if self.find_by_email(user.email) is not None:
            raise ValueError("既に登録済みのメールアドレス")
        self._users.append(user)
 
    @property
    def count(self) -> int:
        return len(self._users)
 
 
class UserRegistrationService:
    """ユーザー登録サービス: ユースケースを調整"""
    def __init__(self, repository: UserRepository):
        self._repository = repository
 
    def register(self, name: str, email_str: str, password_str: str) -> User:
        # 各オブジェクトが自分の責任範囲のバリデーションを行う
        email = EmailAddress(email_str)        # メール形式チェック
        password = Password(password_str)      # パスワード強度チェック
        user = User(name, email, password)     # ユーザー生成
        self._repository.save(user)            # 永続化(重複チェック含む)
        return user
 
 
# 利点:
# - 各クラスが明確な責任を持つ
# - バリデーションがデータと一体化
# - テスト容易(リポジトリをモックに差し替え可能)
# - 機能追加がクラス単位で管理できる

2.2 現実世界のアナロジー

OOPの概念を理解する上で、現実世界のアナロジーが役立つ。

レストランのアナロジー:

  手続き型的思考:
    1. お客さんが来店する
    2. メニューを見せる
    3. 注文を取る
    4. 注文をキッチンに伝える
    5. 料理を作る
    6. 料理を運ぶ
    7. 会計する
    → すべての手順を1つのスクリプトで管理

  OOP的思考:
Customer───→Waiter───→Kitchen
注文する取り次ぐ調理する
│                              │
         │         ┌──────────┐         │
         └────────→│ Cashier  │←────────┘
                   │ 会計する │
                   └──────────┘

    各オブジェクト(人)が自分の責任を遂行する:
    - Customer: メニューを選ぶ、食べる、支払う
    - Waiter: 注文を取る、料理を運ぶ
    - Kitchen: 注文に基づいて料理を作る
    - Cashier: 合計を計算して会計する

    → 新しいメニューが増えても Kitchen だけ変更
    → 支払い方法が変わっても Cashier だけ変更
    → 変更の影響範囲が限定される
// TypeScript: レストランのアナロジーをコードで表現
 
interface MenuItem {
  name: string;
  price: number;
  category: "appetizer" | "main" | "dessert" | "drink";
}
 
class Order {
  private _items: MenuItem[] = [];
  private _status: "pending" | "preparing" | "ready" | "served" = "pending";
 
  addItem(item: MenuItem): void {
    if (this._status !== "pending") {
      throw new Error("注文確定後は追加できません");
    }
    this._items.push(item);
  }
 
  get total(): number {
    return this._items.reduce((sum, item) => sum + item.price, 0);
  }
 
  get items(): ReadonlyArray<MenuItem> {
    return [...this._items]; // 防衛的コピー
  }
 
  get status(): string {
    return this._status;
  }
 
  confirm(): void {
    if (this._items.length === 0) {
      throw new Error("空の注文は確定できません");
    }
    this._status = "preparing";
  }
 
  markReady(): void {
    this._status = "ready";
  }
 
  markServed(): void {
    this._status = "served";
  }
}
 
class Customer {
  private _currentOrder: Order | null = null;
 
  constructor(public readonly name: string) {}
 
  createOrder(): Order {
    this._currentOrder = new Order();
    return this._currentOrder;
  }
 
  get currentOrder(): Order | null {
    return this._currentOrder;
  }
}
 
class Kitchen {
  private _queue: Order[] = [];
 
  receiveOrder(order: Order): void {
    order.confirm();
    this._queue.push(order);
    console.log(`キッチン: 注文を受け付けました(${order.items.length}品)`);
  }
 
  prepareNext(): Order | null {
    const order = this._queue.shift();
    if (order) {
      order.markReady();
      console.log("キッチン: 料理が完成しました");
    }
    return order ?? null;
  }
 
  get pendingOrders(): number {
    return this._queue.length;
  }
}
 
class Waiter {
  constructor(
    private readonly name: string,
    private readonly kitchen: Kitchen,
  ) {}
 
  takeOrder(customer: Customer, items: MenuItem[]): void {
    const order = customer.createOrder();
    for (const item of items) {
      order.addItem(item);
    }
    this.kitchen.receiveOrder(order);
    console.log(`${this.name}: ${customer.name}様の注文を受け付けました`);
  }
 
  serveOrder(order: Order): void {
    order.markServed();
    console.log(`${this.name}: 料理をお持ちしました`);
  }
}
 
class Cashier {
  private _totalRevenue = 0;
 
  checkout(order: Order): number {
    const total = order.total;
    const tax = Math.floor(total * 0.1);
    const grandTotal = total + tax;
    this._totalRevenue += grandTotal;
    console.log(`会計: 小計 ${total}円 + 税 ${tax}円 = ${grandTotal}円`);
    return grandTotal;
  }
 
  get totalRevenue(): number {
    return this._totalRevenue;
  }
}

3. オブジェクトの3要素

オブジェクト = 状態(State)+ 振る舞い(Behavior)+ アイデンティティ(Identity)
BankAccount
状態(State):
- owner: "田中太郎"
- balance: 100000
- accountNumber: "1234567"
振る舞い(Behavior):
- deposit(amount)
- withdraw(amount)
- getBalance()
アイデンティティ(Identity):
- メモリアドレス: 0x7ff...
- 同じ状態でも別のオブジェクト

3.1 状態(State)の管理

状態はオブジェクトが持つデータであり、時間とともに変化し得る。状態管理はOOPの最も重要な関心事の1つである。

// TypeScript: 状態管理の実践例 - ECサイトのショッピングカート
 
interface Product {
  readonly id: string;
  readonly name: string;
  readonly price: number;
  readonly stock: number;
}
 
interface CartItem {
  readonly product: Product;
  quantity: number;
}
 
class ShoppingCart {
  // 状態: カート内の商品リスト
  private _items: Map<string, CartItem> = new Map();
  // 状態: カートの作成日時
  private readonly _createdAt: Date = new Date();
  // 状態: 最終更新日時
  private _updatedAt: Date = new Date();
 
  /**
   * 商品をカートに追加する
   * ビジネスルール: 在庫を超える数量は追加できない
   */
  addItem(product: Product, quantity: number = 1): void {
    if (quantity <= 0) {
      throw new Error("数量は1以上である必要があります");
    }
 
    const existing = this._items.get(product.id);
    const currentQty = existing?.quantity ?? 0;
    const newQty = currentQty + quantity;
 
    if (newQty > product.stock) {
      throw new Error(
        `在庫不足: ${product.name}の在庫は${product.stock}個です`
      );
    }
 
    this._items.set(product.id, { product, quantity: newQty });
    this._updatedAt = new Date();
  }
 
  /**
   * 商品の数量を変更する
   */
  updateQuantity(productId: string, quantity: number): void {
    if (quantity < 0) {
      throw new Error("数量は0以上である必要があります");
    }
 
    if (quantity === 0) {
      this._items.delete(productId);
    } else {
      const item = this._items.get(productId);
      if (!item) {
        throw new Error("カートに存在しない商品です");
      }
      if (quantity > item.product.stock) {
        throw new Error("在庫を超える数量は指定できません");
      }
      item.quantity = quantity;
    }
    this._updatedAt = new Date();
  }
 
  /**
   * カート内の商品を削除する
   */
  removeItem(productId: string): void {
    if (!this._items.has(productId)) {
      throw new Error("カートに存在しない商品です");
    }
    this._items.delete(productId);
    this._updatedAt = new Date();
  }
 
  /**
   * 小計を計算する
   */
  get subtotal(): number {
    let total = 0;
    for (const item of this._items.values()) {
      total += item.product.price * item.quantity;
    }
    return total;
  }
 
  /**
   * 税込合計を計算する
   */
  get totalWithTax(): number {
    return Math.floor(this.subtotal * 1.1);
  }
 
  /**
   * カート内の商品数を返す
   */
  get itemCount(): number {
    let count = 0;
    for (const item of this._items.values()) {
      count += item.quantity;
    }
    return count;
  }
 
  /**
   * カートが空かどうかを返す
   */
  get isEmpty(): boolean {
    return this._items.size === 0;
  }
 
  /**
   * カートの内容を表示する
   */
  display(): string {
    if (this.isEmpty) return "カートは空です";
 
    const lines: string[] = ["=== ショッピングカート ==="];
    for (const item of this._items.values()) {
      const lineTotal = item.product.price * item.quantity;
      lines.push(
        `${item.product.name} x${item.quantity} = ¥${lineTotal.toLocaleString()}`
      );
    }
    lines.push("─".repeat(30));
    lines.push(`小計: ¥${this.subtotal.toLocaleString()}`);
    lines.push(`合計(税込): ¥${this.totalWithTax.toLocaleString()}`);
    return lines.join("\n");
  }
}

3.2 振る舞い(Behavior)の設計

振る舞いはオブジェクトが外部に提供する操作であり、オブジェクトの状態を安全に変更するための手段である。

# Python: 振る舞いの設計 - タスク管理システム
 
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
 
 
class Priority(Enum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3
    CRITICAL = 4
 
 
class TaskStatus(Enum):
    TODO = "todo"
    IN_PROGRESS = "in_progress"
    IN_REVIEW = "in_review"
    DONE = "done"
    CANCELLED = "cancelled"
 
 
class Task:
    """タスク: 状態遷移のルールを振る舞いとして内包"""
 
    # 許可される状態遷移を定義
    _VALID_TRANSITIONS = {
        TaskStatus.TODO: {TaskStatus.IN_PROGRESS, TaskStatus.CANCELLED},
        TaskStatus.IN_PROGRESS: {TaskStatus.IN_REVIEW, TaskStatus.TODO, TaskStatus.CANCELLED},
        TaskStatus.IN_REVIEW: {TaskStatus.DONE, TaskStatus.IN_PROGRESS},
        TaskStatus.DONE: set(),       # 完了後は変更不可
        TaskStatus.CANCELLED: set(),  # キャンセル後は変更不可
    }
 
    def __init__(
        self,
        title: str,
        description: str = "",
        priority: Priority = Priority.MEDIUM,
        due_date: Optional[datetime] = None,
    ):
        if not title.strip():
            raise ValueError("タイトルは必須です")
 
        self._title = title
        self._description = description
        self._priority = priority
        self._status = TaskStatus.TODO
        self._due_date = due_date
        self._created_at = datetime.now()
        self._updated_at = datetime.now()
        self._history: list[tuple[datetime, str]] = []
        self._history.append((self._created_at, f"タスク作成: {title}"))
 
    @property
    def title(self) -> str:
        return self._title
 
    @property
    def status(self) -> TaskStatus:
        return self._status
 
    @property
    def priority(self) -> Priority:
        return self._priority
 
    @property
    def is_overdue(self) -> bool:
        """期限切れかどうかを判定"""
        if self._due_date is None:
            return False
        if self._status in (TaskStatus.DONE, TaskStatus.CANCELLED):
            return False
        return datetime.now() > self._due_date
 
    def start(self) -> None:
        """タスクを開始する"""
        self._transition_to(TaskStatus.IN_PROGRESS, "タスク開始")
 
    def submit_for_review(self) -> None:
        """レビュー依頼する"""
        self._transition_to(TaskStatus.IN_REVIEW, "レビュー依頼")
 
    def complete(self) -> None:
        """タスクを完了する"""
        self._transition_to(TaskStatus.DONE, "タスク完了")
 
    def cancel(self, reason: str = "") -> None:
        """タスクをキャンセルする"""
        self._transition_to(TaskStatus.CANCELLED, f"キャンセル: {reason}")
 
    def send_back(self, reason: str = "") -> None:
        """差し戻す(レビュー → 作業中)"""
        self._transition_to(TaskStatus.IN_PROGRESS, f"差し戻し: {reason}")
 
    def update_priority(self, new_priority: Priority) -> None:
        """優先度を変更する"""
        old = self._priority
        self._priority = new_priority
        self._record_change(f"優先度変更: {old.name}{new_priority.name}")
 
    def _transition_to(self, new_status: TaskStatus, message: str) -> None:
        """状態遷移のルールを強制する"""
        valid = self._VALID_TRANSITIONS.get(self._status, set())
        if new_status not in valid:
            raise ValueError(
                f"無効な状態遷移: {self._status.value}{new_status.value}"
            )
        old_status = self._status
        self._status = new_status
        self._record_change(f"{message} ({old_status.value}{new_status.value})")
 
    def _record_change(self, message: str) -> None:
        """変更履歴を記録する"""
        now = datetime.now()
        self._updated_at = now
        self._history.append((now, message))
 
    def get_history(self) -> list[tuple[datetime, str]]:
        """変更履歴を返す(防衛的コピー)"""
        return list(self._history)
 
    def __str__(self) -> str:
        overdue = " [期限切れ]" if self.is_overdue else ""
        return f"[{self._priority.name}] {self._title} ({self._status.value}){overdue}"
 
 
# 使用例
task = Task("ログイン機能の実装", priority=Priority.HIGH,
            due_date=datetime.now() + timedelta(days=7))
print(task)                    # [HIGH] ログイン機能の実装 (todo)
 
task.start()
print(task)                    # [HIGH] ログイン機能の実装 (in_progress)
 
task.submit_for_review()
print(task)                    # [HIGH] ログイン機能の実装 (in_review)
 
task.complete()
print(task)                    # [HIGH] ログイン機能の実装 (done)
 
# task.start()  # ValueError: 無効な状態遷移: done → in_progress

3.3 アイデンティティ(Identity)の重要性

アイデンティティは、同じ状態を持つ2つのオブジェクトを区別するための概念である。

// Java: アイデンティティと等価性(equality)の違い
 
public class Money {
    private final int amount;
    private final String currency;
 
    public Money(int amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }
 
    // 値オブジェクト: 状態が同じなら等価
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;                    // アイデンティティ一致
        if (obj == null || getClass() != obj.getClass()) return false;
        Money money = (Money) obj;
        return amount == money.amount
            && Objects.equals(currency, money.currency); // 状態で比較
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(amount, currency);
    }
 
    @Override
    public String toString() {
        return amount + " " + currency;
    }
}
 
public class Customer {
    private final String id;   // アイデンティティ: IDで区別
    private String name;
    private String email;
 
    public Customer(String id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
 
    // エンティティ: IDが同じなら同一
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Customer customer = (Customer) obj;
        return Objects.equals(id, customer.id);  // IDのみで比較
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}
 
// 使用例:
// Money: 同じ金額・通貨なら「等価」
Money m1 = new Money(1000, "JPY");
Money m2 = new Money(1000, "JPY");
System.out.println(m1.equals(m2));   // true(値が同じなら等価)
System.out.println(m1 == m2);        // false(異なるオブジェクト)
 
// Customer: 同じIDなら「同一人物」
Customer c1 = new Customer("C001", "田中", "tanaka@example.com");
Customer c2 = new Customer("C001", "田中太郎", "t.tanaka@example.com");
System.out.println(c1.equals(c2));   // true(IDが同じなら同一)
// → 名前やメールが変わっても同一人物
// TypeScript: エンティティと値オブジェクトの区別
 
// 値オブジェクト: 状態で等価性を判定
class Address {
  constructor(
    public readonly prefecture: string,
    public readonly city: string,
    public readonly street: string,
    public readonly zipCode: string,
  ) {}
 
  equals(other: Address): boolean {
    return (
      this.prefecture === other.prefecture &&
      this.city === other.city &&
      this.street === other.street &&
      this.zipCode === other.zipCode
    );
  }
 
  toString(): string {
    return `〒${this.zipCode} ${this.prefecture}${this.city}${this.street}`;
  }
}
 
// エンティティ: IDで同一性を判定
class Employee {
  private _name: string;
  private _address: Address;
  private _department: string;
 
  constructor(
    public readonly employeeId: string,
    name: string,
    address: Address,
    department: string,
  ) {
    this._name = name;
    this._address = address;
    this._department = department;
  }
 
  get name(): string { return this._name; }
  get address(): Address { return this._address; }
  get department(): string { return this._department; }
 
  transfer(newDepartment: string): void {
    this._department = newDepartment;
  }
 
  relocate(newAddress: Address): void {
    this._address = newAddress;
  }
 
  equals(other: Employee): boolean {
    // 社員番号が同じなら同一人物
    return this.employeeId === other.employeeId;
  }
}

コード例

// TypeScript: 銀行口座オブジェクト
class BankAccount {
  // 状態(State)
  private owner: string;
  private balance: number;
  private readonly accountNumber: string;
 
  constructor(owner: string, accountNumber: string, initialBalance: number = 0) {
    this.owner = owner;
    this.accountNumber = accountNumber;
    this.balance = initialBalance;
  }
 
  // 振る舞い(Behavior)
  deposit(amount: number): void {
    if (amount <= 0) throw new Error("入金額は正の数である必要があります");
    this.balance += amount;
  }
 
  withdraw(amount: number): void {
    if (amount > this.balance) throw new Error("残高不足");
    this.balance -= amount;
  }
 
  getBalance(): number {
    return this.balance;
  }
}
 
// アイデンティティ: 同じ状態でも別のオブジェクト
const account1 = new BankAccount("田中", "001", 10000);
const account2 = new BankAccount("田中", "001", 10000);
console.log(account1 === account2); // false(別のオブジェクト)
# Python: 同じ概念
class BankAccount:
    def __init__(self, owner: str, account_number: str, initial_balance: float = 0):
        self._owner = owner
        self._account_number = account_number
        self._balance = initial_balance
 
    def deposit(self, amount: float) -> None:
        if amount <= 0:
            raise ValueError("入金額は正の数である必要があります")
        self._balance += amount
 
    def withdraw(self, amount: float) -> None:
        if amount > self._balance:
            raise ValueError("残高不足")
        self._balance -= amount
 
    @property
    def balance(self) -> float:
        return self._balance
// Java: 同じ概念
public class BankAccount {
    private String owner;
    private double balance;
    private final String accountNumber;
 
    public BankAccount(String owner, String accountNumber, double initialBalance) {
        this.owner = owner;
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }
 
    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("入金額は正の数");
        this.balance += amount;
    }
 
    public void withdraw(double amount) {
        if (amount > this.balance) throw new IllegalStateException("残高不足");
        this.balance -= amount;
    }
 
    public double getBalance() { return balance; }
}

4. メッセージパッシング

OOPの本質はメッセージパッシング:

  手続き型的な考え方:
    result = validate(user_data)    ← 関数にデータを渡す

  OOP的な考え方:
    result = validator.validate(user_data)  ← オブジェクトにメッセージを送る

  違い:
    手続き型: 「誰が」処理するか不明
    OOP:     「validator」が責任を持って処理する

  メッセージパッシングの利点:
    1. 責任の所在が明確
    2. 実装を差し替え可能(ポリモーフィズム)
    3. テスト時にモックに差し替え可能

4.1 メッセージパッシングの実践

メッセージパッシングは単なるメソッド呼び出しではない。「オブジェクトに仕事を依頼する」という考え方である。

// TypeScript: メッセージパッシングの実践例 - 通知システム
 
// 通知を送る「能力」を定義
interface NotificationSender {
  send(recipient: string, message: string): Promise<boolean>;
  readonly channelName: string;
}
 
// メール通知
class EmailSender implements NotificationSender {
  readonly channelName = "Email";
 
  async send(recipient: string, message: string): Promise<boolean> {
    console.log(`メール送信: ${recipient}${message}`);
    // 実際にはSMTPサーバーに接続して送信
    return true;
  }
}
 
// Slack通知
class SlackSender implements NotificationSender {
  readonly channelName = "Slack";
 
  constructor(private readonly webhookUrl: string) {}
 
  async send(recipient: string, message: string): Promise<boolean> {
    console.log(`Slack送信: ${recipient}${message}`);
    // 実際にはwebhook APIを呼び出して送信
    return true;
  }
}
 
// SMS通知
class SmsSender implements NotificationSender {
  readonly channelName = "SMS";
 
  async send(recipient: string, message: string): Promise<boolean> {
    console.log(`SMS送信: ${recipient}${message}`);
    // 実際にはSMS APIを呼び出して送信
    return true;
  }
}
 
// 通知サービス: 送信者を差し替え可能
class NotificationService {
  private senders: NotificationSender[] = [];
 
  addSender(sender: NotificationSender): void {
    this.senders.push(sender);
  }
 
  async notifyAll(recipient: string, message: string): Promise<void> {
    for (const sender of this.senders) {
      try {
        // メッセージパッシング: 各 sender に「送って」とメッセージを送る
        const success = await sender.send(recipient, message);
        if (success) {
          console.log(`${sender.channelName}: 送信成功`);
        }
      } catch (error) {
        console.error(`${sender.channelName}: 送信失敗`);
      }
    }
  }
}
 
// 利用例
const service = new NotificationService();
service.addSender(new EmailSender());
service.addSender(new SlackSender("https://hooks.slack.com/..."));
service.addSender(new SmsSender());
 
// 同じメッセージを全チャネルに送信
// 各 sender が自分の方法で処理する(ポリモーフィズム)
await service.notifyAll("user@example.com", "サーバーアラート発生");

4.2 メッセージパッシングとポリモーフィズムの関係

# Python: メッセージパッシングがポリモーフィズムを実現する
 
from abc import ABC, abstractmethod
from typing import BinaryIO
import json
import csv
import io
 
 
class DataExporter(ABC):
    """データエクスポーターの抽象基底クラス"""
 
    @abstractmethod
    def export(self, data: list[dict]) -> str:
        """データをエクスポートする"""
        pass
 
    @abstractmethod
    def file_extension(self) -> str:
        """ファイル拡張子を返す"""
        pass
 
 
class JsonExporter(DataExporter):
    """JSON形式でエクスポート"""
 
    def export(self, data: list[dict]) -> str:
        return json.dumps(data, ensure_ascii=False, indent=2)
 
    def file_extension(self) -> str:
        return ".json"
 
 
class CsvExporter(DataExporter):
    """CSV形式でエクスポート"""
 
    def export(self, data: list[dict]) -> str:
        if not data:
            return ""
        output = io.StringIO()
        writer = csv.DictWriter(output, fieldnames=data[0].keys())
        writer.writeheader()
        writer.writerows(data)
        return output.getvalue()
 
    def file_extension(self) -> str:
        return ".csv"
 
 
class MarkdownExporter(DataExporter):
    """Markdown表形式でエクスポート"""
 
    def export(self, data: list[dict]) -> str:
        if not data:
            return ""
        headers = list(data[0].keys())
        lines = [
            "| " + " | ".join(headers) + " |",
            "| " + " | ".join(["---"] * len(headers)) + " |",
        ]
        for row in data:
            values = [str(row.get(h, "")) for h in headers]
            lines.append("| " + " | ".join(values) + " |")
        return "\n".join(lines)
 
    def file_extension(self) -> str:
        return ".md"
 
 
class ReportGenerator:
    """レポート生成器: エクスポーターを差し替え可能"""
 
    def __init__(self, exporter: DataExporter):
        self._exporter = exporter
 
    def generate(self, data: list[dict]) -> str:
        # メッセージパッシング: exporter に「エクスポートして」と依頼
        # どの形式で出力するかは exporter が決める
        return self._exporter.export(data)
 
    @property
    def output_extension(self) -> str:
        return self._exporter.file_extension()
 
 
# 使用例
data = [
    {"名前": "田中", "年齢": 25, "部署": "開発"},
    {"名前": "山田", "年齢": 30, "部署": "営業"},
]
 
# 同じデータ、異なるフォーマット
for exporter in [JsonExporter(), CsvExporter(), MarkdownExporter()]:
    generator = ReportGenerator(exporter)
    print(f"--- {generator.output_extension} ---")
    print(generator.generate(data))
    print()

4.3 Smalltalk スタイルのメッセージパッシング

Alan Kay が提唱した本来のメッセージパッシングは、現代の多くの言語のメソッド呼び出しとは概念的に異なる。

Smalltalk のメッセージパッシング:
  → オブジェクトにメッセージを送る
  → オブジェクトがメッセージを受け取り、どう処理するか自分で決める
  → メッセージに対応するメソッドがなければ doesNotUnderstand: が呼ばれる

  3 + 4
  → 3(Integerオブジェクト)に「+」というメッセージと「4」という引数を送る
  → 3 が自分で加算の方法を決める

現代の言語のメソッド呼び出し:
  → コンパイラ/インタプリタが呼び出すメソッドを解決する
  → 存在しないメソッドはコンパイルエラー(静的型付けの場合)

Ruby(Smalltalk の影響が強い):
  → method_missing によるメッセージハンドリング
  → 動的なメソッド解決
# Python: __getattr__ を使ったメッセージパッシング的パターン
 
class FluentQuery:
    """流暢なAPIを持つクエリビルダー"""
 
    def __init__(self):
        self._conditions: list[str] = []
        self._table: str = ""
        self._limit: int | None = None
        self._order_by: str | None = None
 
    def from_table(self, table: str) -> "FluentQuery":
        self._table = table
        return self
 
    def where(self, condition: str) -> "FluentQuery":
        self._conditions.append(condition)
        return self
 
    def limit(self, n: int) -> "FluentQuery":
        self._limit = n
        return self
 
    def order_by(self, column: str) -> "FluentQuery":
        self._order_by = column
        return self
 
    def build(self) -> str:
        query = f"SELECT * FROM {self._table}"
        if self._conditions:
            query += " WHERE " + " AND ".join(self._conditions)
        if self._order_by:
            query += f" ORDER BY {self._order_by}"
        if self._limit:
            query += f" LIMIT {self._limit}"
        return query
 
 
# メソッドチェーン = メッセージの連鎖
query = (
    FluentQuery()
    .from_table("users")
    .where("age >= 18")
    .where("status = 'active'")
    .order_by("created_at DESC")
    .limit(10)
    .build()
)
print(query)
# SELECT * FROM users WHERE age >= 18 AND status = 'active' ORDER BY created_at DESC LIMIT 10

5. OOPが解決する問題

OOPなし(手続き型):
  問題1: グローバル状態の管理困難
    → 誰がどの変数を変更したか追跡不能
    → OOP: カプセル化で状態をオブジェクト内に閉じ込める

  問題2: コードの重複
    → 似た処理を何度も書く
    → OOP: 継承・コンポジションで共通化

  問題3: 変更の影響範囲が不明
    → 1箇所の変更が全体に波及
    → OOP: インターフェースで依存を制御

  問題4: 大規模コードの構造化困難
    → 1万行超えると手続き型は破綻
    → OOP: クラス・パッケージで構造化

5.1 問題と解決の具体例

// TypeScript: OOPが解決する問題の具体例
 
// === 問題1: グローバル状態の管理困難 ===
 
// 手続き型(問題あり)
let globalUserCount = 0;
let globalTotalRevenue = 0;
// ... 100箇所から参照・変更される → カオス
 
// OOP(解決)
class Analytics {
  private _userCount = 0;
  private _totalRevenue = 0;
 
  incrementUsers(): void {
    this._userCount++;
  }
 
  addRevenue(amount: number): void {
    if (amount < 0) throw new Error("収益は正の数");
    this._totalRevenue += amount;
  }
 
  get report(): { users: number; revenue: number } {
    return { users: this._userCount, revenue: this._totalRevenue };
  }
}
// → 状態の変更は Analytics オブジェクト経由のみ
// → 不正な変更を防止できる
 
 
// === 問題2: コードの重複 ===
 
// 手続き型(問題あり)
function validateUserEmail(email: string): boolean {
  return /^[\w.-]+@[\w.-]+\.\w+$/.test(email);
}
function validateAdminEmail(email: string): boolean {
  return /^[\w.-]+@[\w.-]+\.\w+$/.test(email); // 同じロジック!
}
 
// OOP(解決)
class EmailValidator {
  private readonly pattern = /^[\w.-]+@[\w.-]+\.\w+$/;
 
  validate(email: string): boolean {
    return this.pattern.test(email);
  }
}
// → 1箇所にまとめ、再利用
 
 
// === 問題3: 変更の影響範囲が不明 ===
 
// インターフェースで契約を定義
interface PaymentProcessor {
  charge(amount: number, currency: string): Promise<PaymentResult>;
  refund(transactionId: string): Promise<RefundResult>;
}
 
interface PaymentResult {
  transactionId: string;
  success: boolean;
}
 
interface RefundResult {
  success: boolean;
  refundedAmount: number;
}
 
// 実装を差し替えても利用側に影響なし
class StripePaymentProcessor implements PaymentProcessor {
  async charge(amount: number, currency: string): Promise<PaymentResult> {
    // Stripe API を使った実装
    return { transactionId: "stripe_xxx", success: true };
  }
 
  async refund(transactionId: string): Promise<RefundResult> {
    return { success: true, refundedAmount: 1000 };
  }
}
 
class PayPayPaymentProcessor implements PaymentProcessor {
  async charge(amount: number, currency: string): Promise<PaymentResult> {
    // PayPay API を使った実装
    return { transactionId: "paypay_xxx", success: true };
  }
 
  async refund(transactionId: string): Promise<RefundResult> {
    return { success: true, refundedAmount: 1000 };
  }
}
 
// 利用側: PaymentProcessor だけに依存
class CheckoutService {
  constructor(private processor: PaymentProcessor) {}
 
  async checkout(amount: number): Promise<string> {
    const result = await this.processor.charge(amount, "JPY");
    if (!result.success) throw new Error("決済失敗");
    return result.transactionId;
  }
}
// → processor の実装を差し替えても CheckoutService は変更不要
 
 
// === 問題4: 大規模コードの構造化 ===
 
// モジュール構造の例
// src/
//   domain/
//     entities/User.ts, Order.ts, Product.ts
//     value-objects/Money.ts, Address.ts
//     repositories/UserRepository.ts
//   application/
//     services/OrderService.ts, UserService.ts
//   infrastructure/
//     database/PostgresUserRepository.ts
//     external/StripePaymentProcessor.ts
//   presentation/
//     controllers/OrderController.ts
 
// → クラスがファイルの構造化単位になる
// → 依存関係がインターフェースで明確化される

5.2 スケーラビリティの問題

OOPは特にプロジェクトが成長するときにその価値を発揮する。

プロジェクトの成長とパラダイムの適性:

  100行: 手続き型で十分
    → 関数をいくつか定義するだけ
    → クラスは過剰設計

  1,000行: OOPが有効になり始める
    → 状態管理が複雑になる
    → モジュール分割が必要

  10,000行: OOP無しでは管理困難
    → グローバル状態の追跡が不可能に
    → 変更の影響範囲が予測不能に
    → テストが書けなくなる

  100,000行以上: OOP + 設計パターンが必須
    → レイヤードアーキテクチャ
    → 依存性注入(DI)
    → インターフェースによる疎結合
    → テスト戦略の確立

  大規模プロジェクトでの OOP の価値:
    1. コードの構造化 → クラス/パッケージ/モジュール
    2. チーム分担 → クラスの責任境界 = チームの責任境界
    3. テスト容易性 → モック/スタブによる単体テスト
    4. 変更容易性 → インターフェースによる疎結合
    5. 知識の組織化 → ドメインモデルがビジネス知識を表現

6. OOPの適用領域

OOPが得意な領域:
  ✓ GUIアプリケーション(ウィジェット階層)
  ✓ ゲーム開発(エンティティ・コンポーネント)
  ✓ エンタープライズアプリ(ビジネスロジック)
  ✓ フレームワーク設計(拡張ポイントの提供)
  ✓ シミュレーション(現実世界のモデリング)

OOPが不得意な領域:
  ✗ データ変換パイプライン → 関数型が適切
  ✗ 数値計算・科学計算 → 手続き型/配列指向が適切
  ✗ スクリプト・グルーコード → シンプルな手続き型が適切
  ✗ 並行処理 → アクターモデル/関数型が適切

現実のプロジェクト:
  → 複数のパラダイムを組み合わせるのが最適
  → OOP + FP のマルチパラダイムが主流

6.1 GUIアプリケーションでのOOP

GUIフレームワークはOOPの代表的な適用例である。ウィジェットの階層構造と、イベント駆動の仕組みがOOPと自然に対応する。

// TypeScript: GUIコンポーネントの階層(簡易例)
 
abstract class Widget {
  protected _x: number;
  protected _y: number;
  protected _width: number;
  protected _height: number;
  protected _visible: boolean = true;
  protected _parent: Widget | null = null;
  protected _children: Widget[] = [];
 
  constructor(x: number, y: number, width: number, height: number) {
    this._x = x;
    this._y = y;
    this._width = width;
    this._height = height;
  }
 
  addChild(child: Widget): void {
    child._parent = this;
    this._children.push(child);
  }
 
  abstract render(ctx: CanvasRenderingContext2D): void;
 
  renderAll(ctx: CanvasRenderingContext2D): void {
    if (!this._visible) return;
    this.render(ctx);
    for (const child of this._children) {
      child.renderAll(ctx);
    }
  }
 
  hide(): void { this._visible = false; }
  show(): void { this._visible = true; }
}
 
class Panel extends Widget {
  private _backgroundColor: string;
 
  constructor(
    x: number, y: number, w: number, h: number,
    backgroundColor: string = "#ffffff",
  ) {
    super(x, y, w, h);
    this._backgroundColor = backgroundColor;
  }
 
  render(ctx: CanvasRenderingContext2D): void {
    ctx.fillStyle = this._backgroundColor;
    ctx.fillRect(this._x, this._y, this._width, this._height);
  }
}
 
class Label extends Widget {
  constructor(
    x: number, y: number,
    private _text: string,
    private _fontSize: number = 14,
    private _color: string = "#000000",
  ) {
    super(x, y, 0, 0);
  }
 
  set text(value: string) { this._text = value; }
 
  render(ctx: CanvasRenderingContext2D): void {
    ctx.fillStyle = this._color;
    ctx.font = `${this._fontSize}px sans-serif`;
    ctx.fillText(this._text, this._x, this._y);
  }
}
 
class Button extends Widget {
  private _label: string;
  private _onClick: (() => void) | null = null;
  private _isHovered = false;
 
  constructor(
    x: number, y: number, w: number, h: number,
    label: string,
  ) {
    super(x, y, w, h);
    this._label = label;
  }
 
  set onClick(handler: () => void) {
    this._onClick = handler;
  }
 
  handleClick(mouseX: number, mouseY: number): void {
    if (this.containsPoint(mouseX, mouseY) && this._onClick) {
      this._onClick();
    }
  }
 
  private containsPoint(px: number, py: number): boolean {
    return (
      px >= this._x && px <= this._x + this._width &&
      py >= this._y && py <= this._y + this._height
    );
  }
 
  render(ctx: CanvasRenderingContext2D): void {
    ctx.fillStyle = this._isHovered ? "#4488ff" : "#3366cc";
    ctx.fillRect(this._x, this._y, this._width, this._height);
    ctx.fillStyle = "#ffffff";
    ctx.font = "14px sans-serif";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillText(
      this._label,
      this._x + this._width / 2,
      this._y + this._height / 2,
    );
  }
}

6.2 ゲーム開発でのOOP

# Python: ゲームエンティティシステム(簡易例)
 
from abc import ABC, abstractmethod
import math
 
 
class Vector2D:
    """2Dベクトル"""
    def __init__(self, x: float = 0, y: float = 0):
        self.x = x
        self.y = y
 
    def __add__(self, other: "Vector2D") -> "Vector2D":
        return Vector2D(self.x + other.x, self.y + other.y)
 
    def __mul__(self, scalar: float) -> "Vector2D":
        return Vector2D(self.x * scalar, self.y * scalar)
 
    def magnitude(self) -> float:
        return math.sqrt(self.x ** 2 + self.y ** 2)
 
    def normalized(self) -> "Vector2D":
        mag = self.magnitude()
        if mag == 0:
            return Vector2D(0, 0)
        return Vector2D(self.x / mag, self.y / mag)
 
    def distance_to(self, other: "Vector2D") -> float:
        return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
 
 
class GameObject(ABC):
    """ゲームオブジェクトの基底クラス"""
    def __init__(self, position: Vector2D, name: str = ""):
        self.position = position
        self.name = name
        self._active = True
 
    @property
    def is_active(self) -> bool:
        return self._active
 
    def deactivate(self) -> None:
        self._active = False
 
    @abstractmethod
    def update(self, delta_time: float) -> None:
        """毎フレーム呼ばれる更新処理"""
        pass
 
    @abstractmethod
    def render(self) -> str:
        """描画用の文字列を返す"""
        pass
 
 
class Player(GameObject):
    """プレイヤーキャラクター"""
    def __init__(self, position: Vector2D, name: str = "Player"):
        super().__init__(position, name)
        self._hp = 100
        self._max_hp = 100
        self._speed = 5.0
        self._attack_power = 10
        self._level = 1
        self._experience = 0
        self._velocity = Vector2D(0, 0)
 
    @property
    def hp(self) -> int:
        return self._hp
 
    @property
    def is_alive(self) -> bool:
        return self._hp > 0
 
    def move(self, direction: Vector2D) -> None:
        self._velocity = direction.normalized() * self._speed
 
    def take_damage(self, amount: int) -> None:
        self._hp = max(0, self._hp - amount)
        if self._hp == 0:
            self.deactivate()
 
    def heal(self, amount: int) -> None:
        self._hp = min(self._max_hp, self._hp + amount)
 
    def gain_experience(self, amount: int) -> None:
        self._experience += amount
        while self._experience >= self._level * 100:
            self._experience -= self._level * 100
            self._level_up()
 
    def _level_up(self) -> None:
        self._level += 1
        self._max_hp += 10
        self._hp = self._max_hp
        self._attack_power += 2
 
    def update(self, delta_time: float) -> None:
        if not self.is_active:
            return
        self.position = self.position + self._velocity * delta_time
        self._velocity = Vector2D(0, 0)  # 入力がなければ停止
 
    def render(self) -> str:
        return f"[{self.name}] HP:{self._hp}/{self._max_hp} Lv:{self._level} Pos:({self.position.x:.1f},{self.position.y:.1f})"
 
 
class Enemy(GameObject):
    """敵キャラクター"""
    def __init__(self, position: Vector2D, name: str, hp: int, attack: int, speed: float):
        super().__init__(position, name)
        self._hp = hp
        self._attack = attack
        self._speed = speed
        self._target: Player | None = None
 
    def set_target(self, player: Player) -> None:
        self._target = player
 
    def take_damage(self, amount: int) -> None:
        self._hp = max(0, self._hp - amount)
        if self._hp == 0:
            self.deactivate()
 
    def update(self, delta_time: float) -> None:
        if not self.is_active or self._target is None:
            return
 
        # プレイヤーに向かって移動
        direction = Vector2D(
            self._target.position.x - self.position.x,
            self._target.position.y - self.position.y,
        )
        self.position = self.position + direction.normalized() * self._speed * delta_time
 
        # 攻撃範囲内ならダメージ
        if self.position.distance_to(self._target.position) < 1.0:
            self._target.take_damage(self._attack)
 
    def render(self) -> str:
        return f"[{self.name}] HP:{self._hp} Pos:({self.position.x:.1f},{self.position.y:.1f})"
 
 
class GameWorld:
    """ゲームワールド: 全オブジェクトを管理"""
    def __init__(self):
        self._objects: list[GameObject] = []
 
    def add(self, obj: GameObject) -> None:
        self._objects.append(obj)
 
    def update(self, delta_time: float) -> None:
        for obj in self._objects:
            if obj.is_active:
                obj.update(delta_time)
        # 非アクティブオブジェクトを除去
        self._objects = [obj for obj in self._objects if obj.is_active]
 
    def render(self) -> str:
        return "\n".join(obj.render() for obj in self._objects if obj.is_active)

6.3 エンタープライズアプリケーションでのOOP

// Java: エンタープライズアプリケーションの例(注文管理)
 
// エンティティ
public class Order {
    private final String orderId;
    private final String customerId;
    private final List<OrderLine> lines;
    private OrderStatus status;
    private final LocalDateTime createdAt;
    private LocalDateTime updatedAt;
 
    public Order(String orderId, String customerId) {
        this.orderId = orderId;
        this.customerId = customerId;
        this.lines = new ArrayList<>();
        this.status = OrderStatus.DRAFT;
        this.createdAt = LocalDateTime.now();
        this.updatedAt = this.createdAt;
    }
 
    public void addLine(Product product, int quantity) {
        if (status != OrderStatus.DRAFT) {
            throw new IllegalStateException("確定済みの注文には追加できません");
        }
        if (quantity <= 0) {
            throw new IllegalArgumentException("数量は1以上");
        }
        lines.add(new OrderLine(product, quantity));
        updatedAt = LocalDateTime.now();
    }
 
    public BigDecimal getTotal() {
        return lines.stream()
            .map(OrderLine::getSubtotal)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
 
    public void confirm() {
        if (lines.isEmpty()) {
            throw new IllegalStateException("空の注文は確定できません");
        }
        this.status = OrderStatus.CONFIRMED;
        this.updatedAt = LocalDateTime.now();
    }
 
    public void cancel() {
        if (status == OrderStatus.SHIPPED || status == OrderStatus.DELIVERED) {
            throw new IllegalStateException("出荷済みの注文はキャンセルできません");
        }
        this.status = OrderStatus.CANCELLED;
        this.updatedAt = LocalDateTime.now();
    }
 
    // getter省略
}
 
// 値オブジェクト
public class OrderLine {
    private final Product product;
    private final int quantity;
 
    public OrderLine(Product product, int quantity) {
        this.product = product;
        this.quantity = quantity;
    }
 
    public BigDecimal getSubtotal() {
        return product.getPrice().multiply(BigDecimal.valueOf(quantity));
    }
}
 
// 列挙型
public enum OrderStatus {
    DRAFT, CONFIRMED, SHIPPED, DELIVERED, CANCELLED
}
 
// サービス層
public class OrderService {
    private final OrderRepository orderRepository;
    private final InventoryService inventoryService;
    private final NotificationService notificationService;
 
    public OrderService(
        OrderRepository orderRepository,
        InventoryService inventoryService,
        NotificationService notificationService
    ) {
        this.orderRepository = orderRepository;
        this.inventoryService = inventoryService;
        this.notificationService = notificationService;
    }
 
    public void placeOrder(String orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));
 
        // ビジネスルール: 在庫チェック
        for (OrderLine line : order.getLines()) {
            inventoryService.reserve(line.getProduct(), line.getQuantity());
        }
 
        order.confirm();
        orderRepository.save(order);
 
        // 通知
        notificationService.sendOrderConfirmation(order);
    }
}

7. 各言語のOOPスタイル

言語OOPスタイル
Javaクラスベース・純粋OOP
C++クラスベース・マルチパラダイム
Pythonクラスベース・ダックタイピング
TypeScriptクラス + 構造的型付け
Ruby純粋OOP(全てがオブジェクト)
Kotlinクラスベース・データクラス
Swiftプロトコル指向 + クラスベース
Rustトレイトベース(クラスなし)
Go構造体 + インターフェース(クラスなし)
JavaScriptプロトタイプベース + クラス構文

7.1 各言語でのOOP実装比較

同じ「図形の面積計算」を各言語で実装し、OOPスタイルの違いを見る。

// TypeScript: 構造的型付け(ダックタイピング + 型安全)
interface HasArea {
  area(): number;
}
 
class TSCircle implements HasArea {
  constructor(private radius: number) {}
  area(): number { return Math.PI * this.radius ** 2; }
}
 
class TSRectangle implements HasArea {
  constructor(private width: number, private height: number) {}
  area(): number { return this.width * this.height; }
}
 
// 構造的型付け: implements を書かなくても型が一致すればOK
const customShape = {
  area(): number { return 42; }
};
 
function printArea(shape: HasArea): void {
  console.log(`面積: ${shape.area()}`);
}
 
printArea(new TSCircle(5));
printArea(new TSRectangle(3, 4));
printArea(customShape);  // OK: 構造が一致
# Python: ダックタイピング + プロトコル(型ヒント)
from typing import Protocol
import math
 
 
class SupportsArea(Protocol):
    """面積を計算できるもの(プロトコル = 構造的型付け)"""
    def area(self) -> float: ...
 
 
class PyCircle:
    def __init__(self, radius: float):
        self._radius = radius
 
    def area(self) -> float:
        return math.pi * self._radius ** 2
 
 
class PyRectangle:
    def __init__(self, width: float, height: float):
        self._width = width
        self._height = height
 
    def area(self) -> float:
        return self._width * self._height
 
 
def print_area(shape: SupportsArea) -> None:
    """ダックタイピング: area() メソッドがあれば何でも受け付ける"""
    print(f"面積: {shape.area():.2f}")
 
 
print_area(PyCircle(5))
print_area(PyRectangle(3, 4))
// Go: インターフェース + 構造体(クラスなし)
package main
 
import (
    "fmt"
    "math"
)
 
// インターフェース(暗黙的実装)
type Shape interface {
    Area() float64
}
 
// 構造体 + メソッド(クラスの代わり)
type GoCircle struct {
    Radius float64
}
 
func (c GoCircle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}
 
type GoRectangle struct {
    Width  float64
    Height float64
}
 
func (r GoRectangle) Area() float64 {
    return r.Width * r.Height
}
 
// Shape インターフェースを満たすものなら何でも受け取れる
func PrintArea(s Shape) {
    fmt.Printf("面積: %.2f\n", s.Area())
}
 
func main() {
    PrintArea(GoCircle{Radius: 5})
    PrintArea(GoRectangle{Width: 3, Height: 4})
}
// Rust: トレイト + 構造体(クラスなし、継承なし)
use std::f64::consts::PI;
 
trait Shape {
    fn area(&self) -> f64;
 
    // デフォルト実装も可能
    fn describe(&self) -> String {
        format!("面積: {:.2}", self.area())
    }
}
 
struct RustCircle {
    radius: f64,
}
 
impl Shape for RustCircle {
    fn area(&self) -> f64 {
        PI * self.radius * self.radius
    }
}
 
struct RustRectangle {
    width: f64,
    height: f64,
}
 
impl Shape for RustRectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}
 
// トレイト境界でジェネリックに
fn print_area(shape: &dyn Shape) {
    println!("{}", shape.describe());
}
 
fn main() {
    let circle = RustCircle { radius: 5.0 };
    let rect = RustRectangle { width: 3.0, height: 4.0 };
    print_area(&circle);
    print_area(&rect);
}

7.2 プロトタイプベースOOP(JavaScript)

JavaScriptはクラスベースではなく、プロトタイプベースのOOPを採用している。ES2015以降のclass構文はプロトタイプの糖衣構文である。

// JavaScript: プロトタイプベースOOP
 
// === プロトタイプチェーン(内部メカニズム)===
const animal = {
  speak() {
    return `${this.name}が鳴く`;
  },
};
 
const dog = Object.create(animal); // animal をプロトタイプとする
dog.name = "ポチ";
dog.bark = function () {
  return `${this.name}: ワンワン!`;
};
 
console.log(dog.bark());   // ポチ: ワンワン!
console.log(dog.speak());  // ポチが鳴く(プロトタイプから継承)
 
// === ES2015 class 構文(糖衣構文)===
class Animal {
  constructor(name) {
    this.name = name;
  }
 
  speak() {
    return `${this.name}が鳴く`;
  }
}
 
class Dog extends Animal {
  bark() {
    return `${this.name}: ワンワン!`;
  }
}
 
const pochi = new Dog("ポチ");
console.log(pochi.bark());   // ポチ: ワンワン!
console.log(pochi.speak());  // ポチが鳴く
 
// プロトタイプチェーンの確認
console.log(pochi instanceof Dog);     // true
console.log(pochi instanceof Animal);  // true
console.log(Object.getPrototypeOf(pochi) === Dog.prototype); // true

8. OOPの批判と限界

OOPは万能ではない。その限界を理解しておくことで、より適切な設計判断ができる。

OOPに対する主要な批判:

  1. 「バナナを要求したら、バナナを持ったゴリラとジャングルがついてきた」
     — Joe Armstrong(Erlang 作者)
     → 継承による過度な依存関係
     → 1つのクラスを使うために、その親クラス、さらにその親クラス...
     → 解決策: コンポジション重視、薄い継承階層

  2. 過剰な抽象化(Over-engineering)
     → AbstractSingletonProxyFactoryBean 問題
     → 単純な処理にデザインパターンを詰め込む
     → 解決策: YAGNI(You Ain't Gonna Need It)

  3. 状態変更による複雑さ
     → 共有可変状態(Shared Mutable State)は並行処理の天敵
     → オブジェクトの状態が予測不能に変化
     → 解決策: 不変オブジェクト、関数型の要素を取り入れる

  4. 学習コストと生産性
     → 小規模プロジェクトではオーバーヘッドが大きい
     → OOP的に「正しく」書くための設計知識が必要
     → 解決策: 適切な場面で適切に使う

  5. テスト困難な密結合
     → 依存オブジェクトが多いとテストが困難
     → モック地獄(Mock Hell)
     → 解決策: 依存性注入、インターフェースによる疎結合

8.1 OOPの限界を超えるアプローチ

// TypeScript: OOPの限界に対するモダンなアプローチ
 
// === コンポジション over 継承 ===
 
// 悪い例: 深い継承階層
// Animal → Mammal → Pet → Dog → GoldenRetriever
 
// 良い例: コンポジション(機能を組み合わせる)
interface Walkable {
  walk(): void;
}
 
interface Swimmable {
  swim(): void;
}
 
interface Trainable {
  train(command: string): void;
}
 
class DogBehavior implements Walkable, Swimmable, Trainable {
  constructor(private name: string) {}
 
  walk(): void {
    console.log(`${this.name}が散歩する`);
  }
 
  swim(): void {
    console.log(`${this.name}が泳ぐ`);
  }
 
  train(command: string): void {
    console.log(`${this.name}が「${command}」を覚えた`);
  }
}
 
 
// === 不変オブジェクト + ビルダーパターン ===
 
class ImmutableConfig {
  readonly host: string;
  readonly port: number;
  readonly database: string;
  readonly maxConnections: number;
  readonly timeout: number;
 
  private constructor(builder: ConfigBuilder) {
    this.host = builder.host;
    this.port = builder.port;
    this.database = builder.database;
    this.maxConnections = builder.maxConnections;
    this.timeout = builder.timeout;
  }
 
  static builder(): ConfigBuilder {
    return new ConfigBuilder();
  }
 
  // 既存の設定を元に一部変更した新しい設定を作成
  withHost(host: string): ImmutableConfig {
    return ImmutableConfig.builder()
      .setHost(host)
      .setPort(this.port)
      .setDatabase(this.database)
      .setMaxConnections(this.maxConnections)
      .setTimeout(this.timeout)
      .build();
  }
}
 
class ConfigBuilder {
  host = "localhost";
  port = 5432;
  database = "mydb";
  maxConnections = 10;
  timeout = 5000;
 
  setHost(host: string): this { this.host = host; return this; }
  setPort(port: number): this { this.port = port; return this; }
  setDatabase(db: string): this { this.database = db; return this; }
  setMaxConnections(n: number): this { this.maxConnections = n; return this; }
  setTimeout(ms: number): this { this.timeout = ms; return this; }
 
  build(): ImmutableConfig {
    // @ts-ignore: private constructor access for builder
    return new ImmutableConfig(this);
  }
}

9. OOPを学ぶためのロードマップ

OOP学習のロードマップ:

  Level 1: 基礎概念
    □ クラスとオブジェクトの関係
    □ コンストラクタとインスタンス化
    □ フィールドとメソッド
    □ アクセス修飾子(public, private, protected)

  Level 2: 4つの柱
    □ カプセル化(情報隠蔽、バンドリング)
    □ 継承(is-a 関係、メソッドオーバーライド)
    □ ポリモーフィズム(インターフェース、抽象クラス)
    □ 抽象化(複雑さの隠蔽、契約の定義)

  Level 3: 設計原則
    □ SOLID 原則
    □ Tell, Don't Ask
    □ Law of Demeter
    □ Composition over Inheritance

  Level 4: デザインパターン
    □ 生成パターン(Factory, Builder, Singleton)
    □ 構造パターン(Adapter, Decorator, Proxy)
    □ 振る舞いパターン(Strategy, Observer, Command)

  Level 5: アーキテクチャ
    □ レイヤードアーキテクチャ
    □ ドメイン駆動設計(DDD)
    □ クリーンアーキテクチャ
    □ 依存性注入(DI)

  Level 6: マルチパラダイム
    □ OOP + FP の融合
    □ リアクティブプログラミング
    □ アクターモデル
    □ イベントソーシング

FAQ

Q1: このトピックを学ぶ上で最も重要なポイントは何ですか?

実践的な経験を積むことが最も重要です。理論だけでなく、実際にコードを書いて動作を確認することで理解が深まります。

Q2: 初心者がよく陥る間違いは何ですか?

基礎を飛ばして応用に進むことです。このガイドで説明している基本概念をしっかり理解してから、次のステップに進むことをお勧めします。

Q3: 実務ではどのように活用されていますか?

このトピックの知識は、日常的な開発業務で頻繁に活用されます。特にコードレビューやアーキテクチャ設計の際に重要になります。


まとめ

概念 ポイント
OOPの本質 データと振る舞いをオブジェクトに統合
3要素 状態 + 振る舞い + アイデンティティ
4つの柱 カプセル化、継承、ポリモーフィズム、抽象化
メッセージ オブジェクト間のメソッド呼び出し
得意分野 GUI、ゲーム、エンタープライズ、フレームワーク
限界 過剰な抽象化、共有可変状態、継承の複雑さ
現実 OOP + FP のマルチパラダイムが主流
学習 基礎 → 4本柱 → SOLID → パターン → アーキテクチャ

次に読むべきガイド


参考文献

  1. Kay, A. "The Early History of Smalltalk." ACM SIGPLAN Notices, 1993.
  2. Gamma, E. et al. "Design Patterns." Addison-Wesley, 1994.
  3. Martin, R. "Clean Code." Prentice Hall, 2008.
  4. Bloch, J. "Effective Java." 3rd Ed, Addison-Wesley, 2018.
  5. Armstrong, J. "Coders at Work." Apress, 2009.
  6. Meyer, B. "Object-Oriented Software Construction." 2nd Ed, Prentice Hall, 1997.