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

合成 vs 継承 ごうせい vs けいしょう

オブジェクト指向コンポジション継承has-a関係is-a関係依存関係
合成 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チェック」で継承の罠を回避

継承を使う前に、「子クラスは親クラスの一種だと胸を張って言えるか?」を自問自答しましょう。

  • Dog is a Animal → OK、継承してよい
  • AdminUser is a User → OK、継承してよい
  • Car is a Engine → NG、合成を使おう
  • Logger is a File → 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部品を差し替えても他の部品に影響なし
継承(Inheritance) 合成(Composition) Animal Dog GoldenRetriever ▲ 変更が上から連鎖する ⚠ 脆弱な基底クラス問題 Dog Voice Legs Name 差し替え可 差し替え可 差し替え可 ▲ 部品を独立して変更できる ✅ 疎結合・テストしやすい Voiceを「CatVoice」に差し替えると 猫の声で鳴く犬クラスの完成! (継承では実現しにくい柔軟な変更) Animalに「breathe()」を追加すると Dog・GoldenRetrieverに自動で波及! (意図しない挙動変化のリスク)

実務上の判断フローチャート

新しいクラスを作りたい


「AはBの一種(is-a)」と言える?
   YES  │  NO
        │   └─→ 合成を使う ✅

「将来も必ずそう言える?」
   YES  │  NO
        │   └─→ 合成を使う ✅

「親クラスの実装詳細に依存してよい?」
   YES  │  NO
        │   └─→ 合成を使う ✅

  継承を使う ✅

関連用語