概要
オブジェクト指向において、「コードを再利用する」という言葉は常に 継承(Inheritance) と対で語られる。
だが、継承は誤用されると脆弱で変更に弱い設計となるリスクを孕む。
この課題に対する現代的解法が、コンポジション(Composition) や Strategy パターンといった手法である。
本稿では、継承と委譲の違い、Strategyパターンの導入意義、
そして実装レベルでどのように使い分け、選択するべきかを具体的に解説する。
1. 継承とは何か?:構造の再利用
class Animal {
move() {
console.log("Moving...")
}
}
class Dog extends Animal {
bark() {
console.log("Woof!")
}
}
- 子クラスは親クラスの構造と振る舞いをそのまま持つ
- しかし、親の変更が子に波及しやすい
→ 密結合の代表例
2. 委譲とは何か?:責務の移譲
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パターン:振る舞いを差し替え可能に
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. 設計判断フロー
① 振る舞いを複数切り替える必要がある? → YES → Strategy
② 再利用したいロジックがあるが、親子の関係が弱い? → YES → 委譲
③ 継承先は、基底クラスの契約を厳格に守れる? → YES → 継承可
④ 振る舞いの追加が頻繁に起きる? → YES → 継承より委譲/Strategyが安全
6. 実務におけるパターン
✅ UIコンポーネントにおける振る舞い注入(Reactのprops
戦略)
<Button onClick={customClickHandler}>Click</Button>
→ Strategy的構造(挙動を外から注入)
✅ サービスの切り替え(インフラ層)
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は再利用と拡張性を両立するデザイン戦略
設計とは、
“変更の可能性に備えて構造を柔軟に保ち、責務を分離し、拡張可能な形でロジックを分配する技法” である。