継承よりコンポジションを選ぶべき理由と実践例

オブジェクト指向プログラミングにおいて、「継承」より「コンポジション」を選択する設計手法が注目されています。この資料では、両者の違い、メリットとデメリット、Unityにおける実践例を通して、なぜコンポジションが推奨されるのかを解説します。


1. 継承 (Inheritance)

特徴

  • あるクラスを基に新しいクラスを作成し、そのクラスの性質や動作を引き継ぐ仕組み。
  • 「○○は××である (is-a)」の関係を表現。

メリット

  • コードの再利用: 共通の機能を親クラスにまとめることで効率的に再利用。
  • 階層構造の表現: オブジェクトの階層関係を明示的に表現可能。

デメリット

  • 柔軟性の欠如: 親クラスに強く依存し、変更が困難。
  • 強い結合: 親クラスの変更が子クラスに波及。
  • 多重継承の問題: 設計が複雑化し、バグの温床に。

2. コンポジション (Composition)

特徴

  • 複数のオブジェクトを組み合わせて、新しい機能や振る舞いを実現。
  • 「○○は××を持っている (has-a)」の関係を表現。

メリット

  • 柔軟性: クラス間の結合が緩く、再利用性や変更に強い。
  • 動的な振る舞い: 実行時に異なるコンポーネントを組み合わせ可能。
  • 単一責任原則(SRP): 各コンポーネントが独立して役割を果たすため、設計がシンプル。

デメリット

  • 初学者には難解: オブジェクト間の依存関係の設計が複雑化することも。
  • コード量の増加: クラスやオブジェクト数が増える可能性。

3. Unityにおける実践例

Unityはコンポジションを基本とした設計を採用しており、GameObjectとComponentを組み合わせて柔軟なシステムを構築します。

継承を使った例

public class Enemy : MonoBehaviour
{
    public void Attack() { ... }
}

public class Boss : Enemy
{
    public void SpecialAttack() { ... }
}

問題点:

  • Boss クラスが Enemy に強く依存し、汎用性が低下。

コンポジションを使った例

public class Enemy : MonoBehaviour
{
    public IAttackStrategy AttackStrategy { get; set; }

    public void PerformAttack()
    {
        AttackStrategy.Attack();
    }
}

public interface IAttackStrategy
{
    void Attack();
}

public class NormalAttack : IAttackStrategy
{
    public void Attack() { ... }
}

public class SpecialAttack : IAttackStrategy
{
    public void Attack() { ... }
}

メリット:

  • AttackStrategy を実行時に変更可能。
  • 再利用性が高く、拡張にも柔軟に対応。

4. どちらを選ぶべきか?

継承が適切な場合

  • 「is-a」の関係が自然で明確な場合。
  • クラスの階層構造が安定しており、変更の可能性が低い場合。

コンポジションが適切な場合

  • 動的に振る舞いを変更する必要がある場合。
  • 再利用性や柔軟性を重視したい場合。
  • 設計が頻繁に変更される可能性がある場合。

5. 結論

「継承かコンポジションか」の選択は、プロジェクトの要件や設計思想によります。ただし、柔軟性や変更への対応を重視する場合、特にUnityのような環境では「コンポジション」が推奨されます。

適切な手法を選択することで、設計の品質を向上させ、保守性の高いコードを実現できます。