合成 vs 継承 ごうせい vs けいしょう
合成 vs 継承とは
オブジェクト指向プログラミング(OOP)でコードを再利用するとき、大きく2つのアプローチがあります。ひとつは継承(Inheritance)、もうひとつは合成(Composition)です。どちらも「すでに書いたコードを別の場所で使い回す」ための技術ですが、設計の思想とリスクが大きく異なります。
継承は「クラスA(親)の性質をクラスB(子)がそのまま受け継ぐ」仕組みです。「犬は動物である(Dog is-a Animal)」という関係性で表現できるとき、継承は自然にフィットします。一方合成は「クラスBがクラスAのインスタンスを内部に持つ」仕組みです。「車はエンジンを持つ(Car has-a Engine)」という関係性で表現できるとき、合成が適しています。
1994年に出版されたGoF(Gang of Four)の名著『デザインパターン』で「クラス継承よりオブジェクト合成を優先せよ」という原則が明示されて以来、継承の乱用を避ける動きが広まりました。現代のフレームワークやアーキテクチャ設計においても、この原則は変わらず重要視されています。
is-a関係 vs has-a関係:使い分けの核心
継承と合成を正しく使い分けるカギは、クラス間の関係を「is-a(〜は〜である)」で語れるか、「has-a(〜は〜を持つ)」で語れるかを見極めることです。
| 観点 | 継承(Inheritance) | 合成(Composition) |
|---|---|---|
| 関係性 | is-a(〜は〜である) | has-a(〜は〜を持つ) |
| 例 | 犬は動物である | 車はエンジンを持つ |
| 結合度 | 強い(密結合) | 弱い(疎結合) |
| 柔軟性 | 低い(変更が波及しやすい) | 高い(部品を交換しやすい) |
| コード量 | 少なくなりやすい | やや増えることがある |
| テスト容易性 | 難しくなりがち | 部品単位でテストしやすい |
| 多重継承 | 言語により制限あり | 制限なし |
✅ 覚え方:「is-aチェック」で継承の罠を回避
継承を使う前に、「子クラスは親クラスの一種だと胸を張って言えるか?」を自問自答しましょう。
- ✅
Dogis aAnimal→ OK、継承してよい - ✅
AdminUseris aUser→ OK、継承してよい - ❌
Caris aEngine→ NG、合成を使おう - ❌
Loggeris aFile→ NG、ファイルを「持つ」だけ
このチェックを「is-aテスト」と呼びます。失敗したら合成を選ぶのが原則です。
🔢 継承の深さと危険度
継承の深さ(クラス階層のレベル数)と保守コストの目安
レベル1: 親 → 子 ✅ 問題なし
レベル2: 親 → 子 → 孫 ✅ まだ許容範囲
レベル3: 親 → 子 → 孫 → ひ孫 ⚠️ 要注意
レベル4以上: 🚨 疎結合への設計見直しを推奨
継承の階層が深くなるほど、親クラスの変更が子孫すべてに波及して「意図しない壊れ方」が起きやすくなります。これを脆弱な基底クラス問題(Fragile Base Class Problem)と呼びます。
歴史と背景
- 1966年 — Simula 67(世界初のOOP言語)が継承の概念を導入。コード再利用の画期的な方法として登場
- 1980年代 — Smalltalk、C++ が継承を広め、OOPが主流言語に採用されていく
- 1994年 — GoF『デザインパターン』にて「継承よりオブジェクト合成を優先せよ」を原則として明文化。継承の乱用が問題視され始める
- 1990年代後半〜2000年代 — Javaの普及とともに深い継承階層の「スパゲッティ継承」が現場に蔓延し、保守困難な事例が多発
- 2000年代 — Rubyのmixin、Javaのインターフェース活用など、継承に頼らない設計パターンが普及
- 2010年代 — Goなど「継承を持たない言語」が登場。合成・インターフェースを中心とした設計が再評価される
- 2015年以降 — Reactなど現代フレームワークが「継承ではなくコンポジション(合成)」をコンポーネント設計の原則として明示
合成と継承の構造比較
コードで見るとふたつのアプローチの違いがより鮮明になります。
【継承のイメージ(Java風)】
Animal
└── Dog(extends Animal)
└── GoldenRetriever(extends Dog)
# GoldenRetrieverはAnimalの変更が自動で波及してくる
# 「親の実装」に強く依存している状態
【合成のイメージ(Java風)】
Dog
├── has-a: Engine ← 違う、これは車
├── has-a: Voice(鳴き声の実装を持つ)
└── has-a: Legs(歩行ロジックを持つ)
# Dogは部品(VoiceやLegs)を「使う」だけ
# Voice部品を差し替えても他の部品に影響なし
実務上の判断フローチャート
新しいクラスを作りたい
│
▼
「AはBの一種(is-a)」と言える?
YES │ NO
│ └─→ 合成を使う ✅
▼
「将来も必ずそう言える?」
YES │ NO
│ └─→ 合成を使う ✅
▼
「親クラスの実装詳細に依存してよい?」
YES │ NO
│ └─→ 合成を使う ✅
▼
継承を使う ✅
関連用語
- ./050-object-oriented-programming.md — オブジェクト指向プログラミング全体の考え方
- ./052-interface-abstract-class.md — インターフェース・抽象クラス:継承の代替手段として活用
- ./053-design-patterns.md — デザインパターン:GoFが合成を推奨したきっかけの名著
- ./054-dependency-injection.md — 依存性注入:合成と組み合わせて使う設計技法
- ./055-solid-principles.md — SOLID原則:継承・合成を正しく使うための設計原則
- ./056-coupling-cohesion.md — 結合度と凝集度:合成が「疎結合」を実現する理由
- ./057-mixin.md — Mixin:継承と合成の中間的な再利用手法