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);
}
}
使い方
- プレイヤーオブジェクトに
MoveComponent
とJumpComponent
をアタッチします。 - 必要に応じて他のオブジェクトにも追加可能です(例: 敵キャラクターが移動だけを持つなど)。
結論
「継承」と「合成」はどちらも強力な設計手法ですが、それぞれ得意な場面が異なります。Unityのコンポーネントベース設計では、特に合成が推奨されるケースが多いですが、適切な場面では継承も十分に活用できます。
設計の指針
- 明確な「is-a」関係がある場合は継承。
- 柔軟性や拡張性が必要な場合は合成。
- プロジェクトの要件に応じて、両者を併用することで最適な設計を実現します。
ディスカッション
コメント一覧
まだ、コメントがありません