実装レベルでのOOPとコンポジション：継承 vs 委譲 vs Strategy

## 概要

オブジェクト指向において、「コードを再利用する」という言葉は常に **継承（Inheritance）** と対で語られる。  
だが、継承は誤用されると**脆弱で変更に弱い設計**となるリスクを孕む。

この課題に対する現代的解法が、**コンポジション（Composition）** や **Strategy パターン**といった手法である。

本稿では、継承と委譲の違い、Strategyパターンの導入意義、  
そして実装レベルで**どのように使い分け、選択するべきか**を具体的に解説する。

---

## 1. 継承とは何か？：構造の再利用

```ts
class Animal {
  move() {
    console.log("Moving...")
  }
}

class Dog extends Animal {
  bark() {
    console.log("Woof!")
  }
}
```

- 子クラスは親クラスの**構造と振る舞いをそのまま持つ**
- しかし、**親の変更が子に波及**しやすい  
→ **密結合の代表例**

---

## 2. 委譲とは何か？：責務の移譲

```ts
class Engine {
  start() {
    console.log("Engine started")
  }
}

class Car {
  constructor(private engine: Engine) {}

  drive() {
    this.engine.start()
    console.log("Car is moving")
  }
}
```

- `Car` は `Engine` を内部に持ち、**振る舞いを委譲**  
- コンポーネントを差し替えやすく、**柔軟性が高い**

---

## 3. Strategyパターン：振る舞いを差し替え可能に

```ts
interface SortingStrategy {
  sort(data: number[]): number[]
}

class QuickSort implements SortingStrategy {
  sort(data: number[]) {
    return [...data].sort((a, b) => a - b) // 仮にQuickSortとする
  }
}

class BubbleSort implements SortingStrategy {
  sort(data: number[]) {
    // シンプルなバブルソート
    const result = [...data]
    for (let i = 0; i < result.length; i++) {
      for (let j = 0; j < result.length - 1; j++) {
        if (result[j] > result[j + 1]) {
          [result[j], result[j + 1]] = [result[j + 1], result[j]]
        }
      }
    }
    return result
  }
}

class DataProcessor {
  constructor(private strategy: SortingStrategy) {}

  process(data: number[]) {
    return this.strategy.sort(data)
  }
}
```

- 実行時に**挙動を差し替え可能**
- 継承よりも**依存の明示性が高く、テスト・拡張に強い**

---

## 4. 継承・委譲・Strategyの比較

| 観点           | 継承                       | 委譲                        | Strategy                         |
|----------------|----------------------------|-----------------------------|----------------------------------|
| 再利用性       | 高いが密結合                | 高い（構成の自由あり）       | 非常に高い（動的切り替え可能）     |
| 柔軟性         | 低い（構造固定）            | 中（部分的に交換可能）       | 高（振る舞いごと差し替え可能）     |
| 拡張コスト     | 高い（親子両方の変更必要）    | 低い                         | 低い（新しい戦略の追加だけ）       |
| テストのしやすさ | 低い（内部依存強い）         | 中（注入可能）               | 高い（単体テストしやすい）         |

---

## 5. 設計判断フロー

```text
① 振る舞いを複数切り替える必要がある？ → YES → Strategy

② 再利用したいロジックがあるが、親子の関係が弱い？ → YES → 委譲

③ 継承先は、基底クラスの契約を厳格に守れる？ → YES → 継承可

④ 振る舞いの追加が頻繁に起きる？ → YES → 継承より委譲/Strategyが安全
```

---

## 6. 実務におけるパターン

### ✅ UIコンポーネントにおける振る舞い注入（Reactの`props`戦略）

```tsx
<Button onClick={customClickHandler}>Click</Button>
```

→ Strategy的構造（挙動を外から注入）

---

### ✅ サービスの切り替え（インフラ層）

```ts
interface EmailService { send(): void }

class SESMailer implements EmailService { /* ... */ }
class LocalMailer implements EmailService { /* ... */ }

const service = isProd ? new SESMailer() : new LocalMailer()
```

→ 委譲 or Strategyパターンとして実装

---

## よくある誤解と対策

### ❌ 継承はDRY（Don't Repeat Yourself）だから正義

→ ✅ DRYのために設計が崩れることもある。**共通化は「振る舞いの意味」が等しいときに限る**

---

### ❌ 委譲はオーバーエンジニアリング

→ ✅ **構造の柔軟性と責務分離を考慮すれば、委譲はむしろ健全**

---

### ❌ Strategyは難しい・重たい

→ ✅ 小規模な Strategy も十分価値がある  
→ `関数の注入` すら Strategy の一種とみなせる

---

## 結語

オブジェクト指向において再利用や柔軟性を追求するなら、  
**「継承よりも構造の制御を重視すべき」**という原則が必要となる。

- **継承は強力だが脆い**
- **委譲は柔軟かつ秩序的**
- **Strategyは再利用と拡張性を両立するデザイン戦略**

**設計とは、  
“変更の可能性に備えて構造を柔軟に保ち、責務を分離し、拡張可能な形でロジックを分配する技法” である。**