Unityにおける継承と合成の使い分け:技術資料

概要

Unityはコンポーネントベースの設計思想を採用しており、オブジェクトに機能を付与する方法として「継承」と「合成」の両方が利用可能です。本資料では、それぞれの利点と欠点を解説し、適材適所で使い分けるための指針を示します。


継承と合成の違い

要素継承合成
関係性is-a(親クラスの一種)has-a(機能の組み合わせ)
柔軟性階層が深くなると低下必要なコンポーネントだけで柔軟
再利用性再利用には子クラス作成が必要そのまま使い回しが可能
保守性親クラス変更の影響が大きい各コンポーネントが独立
適用場面共通ロジックの継承やポリモーフィズムに最適機能単位で構成する柔軟な設計に最適

継承の利点と適用場面

利点

「is-a」関係の表現

  • 子クラスが親クラスの一種である場合、継承を使用すると直感的な設計が可能。
  • 例: 「犬」は「動物」の一種 (Dog : Animal)。

コードの共通化

  • 親クラスに共通のロジックを定義することで、コードを再利用できます。

ポリモーフィズムの活用

  • 親クラスを通じて子クラスの異なる振る舞いを共通化できます。
  • 例: Animal.Speak() を子クラスごとに異なる実装で上書き (Dog.Speak()Cat.Speak())。

安定したドメインに有効

  • 親クラスの仕様が変更されにくい場合、継承による設計がシンプルで効果的です。

適用場面

  • ドメインが安定している場合
    仕様変更が少なく、親クラスがしっかり設計されているケース。
  • ポリモーフィズムを活用する場合
    同じインターフェースを持つ複数の子クラスを操作する場面。
  • 「is-a」関係が明確な場合
    子クラスが親クラスの一種であることが自明な場合。

継承を使った例

注意:Updateメソッドを省略しているため、このままのコードでは実行できません

BaseCharacter.cs

using UnityEngine;

public class BaseCharacter : MonoBehaviour
{
    public float speed = 5f;

    public virtual void Move()
    {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        transform.Translate(new Vector3(h, 0, v) * speed * Time.deltaTime);
    }
}

PlayerCharacter.cs

using UnityEngine;

public class PlayerCharacter : BaseCharacter
{
    public float jumpForce = 5f;

    public override void Move()
    {
        base.Move(); // 基底クラスの移動処理を呼び出す

        if (Input.GetButtonDown("Jump"))
        {
            Jump();
        }
    }

    private void Jump()
    {
        GetComponent<Rigidbody>().AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
    }
}

問題点

  • 親クラスの仕様変更が子クラスに影響を与えやすい。
  • 階層が深くなるとコードの理解や保守が難しくなる。

合成の利点と適用場面

利点

柔軟性

  • 機能を独立したコンポーネントとして実装することで、必要な機能を自由に組み合わせられます。

再利用性

  • 各コンポーネントを他のゲームオブジェクトにも簡単に使い回せます。

単一責任の原則(SRP)

  • 各コンポーネントが1つの責任(例: 移動、ジャンプ)にのみ集中しているため、コードがシンプルで理解しやすくなります。

保守性

  • コンポーネントが独立しているため、他のスクリプトに影響を与えずに変更や追加が可能です。

適用場面

  • 多様な機能を持つオブジェクトを設計する場合
    必要な機能だけを柔軟に組み合わせたい場面。
  • プロジェクトが大規模化する場合
    各機能を独立させて管理した方が保守が容易になるケース。
  • ドメインが頻繁に変化する場合
    仕様が変わりやすい機能を独立して実装することで、変更の影響を最小限に抑えます。

合成を使った例

MoveComponent.cs

using UnityEngine;

public class MoveComponent : MonoBehaviour
{
    public float speed = 5f;

    void Update()
    {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        transform.Translate(new Vector3(h, 0, v) * speed * Time.deltaTime);
    }
}

JumpComponent.cs

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class JumpComponent : MonoBehaviour
{
    public float jumpForce = 5f;

    void Update()
    {
        if (Input.GetButtonDown("Jump"))
        {
            Jump();
        }
    }

    private void Jump()
    {
        GetComponent<Rigidbody>().AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
    }
}

使い方

  1. プレイヤーオブジェクトに MoveComponentJumpComponent をアタッチします。
  2. 必要に応じて他のオブジェクトにも追加可能です(例: 敵キャラクターが移動だけを持つなど)。

結論

「継承」と「合成」はどちらも強力な設計手法ですが、それぞれ得意な場面が異なります。Unityのコンポーネントベース設計では、特に合成が推奨されるケースが多いですが、適切な場面では継承も十分に活用できます。

設計の指針

  • 明確な「is-a」関係がある場合は継承
  • 柔軟性や拡張性が必要な場合は合成
  • プロジェクトの要件に応じて、両者を併用することで最適な設計を実現します。