ターゲットにダメージを与える処理の実装 (依存性逆転の原則を使用)
この資料では、Unityでミサイルがターゲットにダメージを与える処理を、依存性逆転の原則(Dependency Inversion Principle: DIP)を適用して実装する手法について説明します。インターフェースを使用することで、設計の柔軟性と拡張性を高め、具体的なクラスへの依存をなくす方法を紹介します。また、依存性逆転を使用しない場合の設計とその問題点についても比較します。
前提
次の資料を踏まえた解説です
依存性逆転の原則 (DIP) とは?
依存性逆転の原則 (DIP) では、クラス間の依存関係を具体的な実装(詳細)に対してではなく、抽象(インターフェース)に依存させます。これにより、クラス同士が互いに影響を受けにくくなり、変更や拡張に強い設計が可能となります。
原則のポイント
- 高レベルのモジュールは低レベルのモジュールに依存してはならない。どちらも抽象(インターフェース)に依存すべきである。
- 抽象は詳細に依存してはならない。詳細(具体的な実装)が抽象に依存するべきである。
依存性逆転を使わない場合の実装
まず、依存性逆転を使用しない場合のミサイルがターゲットにダメージを与える処理を見てみましょう。
1. Health
クラスの実装
ターゲットのHPを管理するクラスです。このクラスは、ターゲットがダメージを受けたときの処理を行います。
public class Health : MonoBehaviour
{
public int maxHealth = 100;
private int currentHealth;
void Start()
{
currentHealth = maxHealth;
}
public void TakeDamage(int amount)
{
currentHealth -= amount;
if (currentHealth <= 0)
{
Die();
}
}
void Die()
{
Debug.Log("ターゲットが破壊されました!");
Destroy(gameObject);
}
}
2. Missile
クラスの実装
ミサイルがターゲットにダメージを与える処理は、Health
クラスに直接依存しています。
public class Missile : MonoBehaviour
{
public Transform target;
public float speed = 5.0f;
public int damage = 10;
void Explode()
{
Health targetHealth = target.GetComponent<Health>(); // Healthに直接依存
if (targetHealth != null)
{
targetHealth.TakeDamage(damage);
}
Destroy(gameObject);
}
}
問題点:
- 拡張性が低い: ターゲットが
Health
クラスを持たない場合、例えば異なるダメージ処理が必要なターゲット(シールドを持つ敵など)を導入する場合、このMissile
クラスを変更しなければなりません。 - テストが困難:
Missile
クラスはHealth
クラスに直接依存しているため、単体テスト時にモックオブジェクトを使いにくく、実装の変更がテストに影響を与えます。
依存性逆転を取り入れた設計
次に、インターフェースを使用し、依存性逆転の原則に基づいた設計を紹介します。この設計では、ミサイルがターゲットに依存せず、ターゲットがどのような実装を持っているかに関わらず、抽象的な「ダメージ処理」を行うことができます。
1. IHealth
インターフェースの定義
まず、IHealth
インターフェースを定義します。このインターフェースを実装することで、どのターゲットも同じダメージ処理を共有できます。
public interface IHealth
{
void TakeDamage(int amount);
}
2. Health
クラスの実装
IHealth
インターフェースを実装するHealth
クラスです。これにより、ミサイルは具体的なHealth
クラスに依存することなく、抽象的なIHealth
に依存します。
public class Health : MonoBehaviour, IHealth
{
public int maxHealth = 100;
private int currentHealth;
void Start()
{
currentHealth = maxHealth;
}
public void TakeDamage(int amount)
{
currentHealth -= amount;
if (currentHealth <= 0)
{
Die();
}
}
void Die()
{
Debug.Log("ターゲットが破壊されました!");
Destroy(gameObject);
}
}
3. Missile
クラスの実装
Missile
クラスは、IHealth
インターフェースに依存してターゲットにダメージを与えるため、Health
以外のターゲットにも対応できます。
public class Missile : MonoBehaviour
{
public Transform target;
public float speed = 5.0f;
public int damage = 10;
void Explode()
{
IHealth targetHealth = target.GetComponent<IHealth>(); // IHealthインターフェースに依存
if (targetHealth != null)
{
targetHealth.TakeDamage(damage);
}
Destroy(gameObject);
}
}
4. 追加ターゲット:Shield
クラスの例
例えば、シールドを持つ敵に対して、シールドの耐久値を減らすクラスを追加できます。IHealth
インターフェースを実装することで、同じミサイルクラスがシールドにもダメージを与えることができます。
public class Shield : MonoBehaviour, IHealth
{
public int shieldPoints = 50;
public void TakeDamage(int amount)
{
shieldPoints -= amount;
if (shieldPoints <= 0)
{
Destroy(gameObject); // シールド破壊
}
}
}
比較: 依存性逆転を取り入れた設計 vs 従来の設計
特徴 | 従来の設計 | 依存性逆転を取り入れた設計 |
---|---|---|
依存関係 | Missile クラスはHealth クラスに依存 | IHealth インターフェースに依存 |
柔軟性 | Health クラスに固有のダメージ処理のみ | さまざまなクラスに対応可能 |
拡張性 | 新たなダメージ処理の追加が困難 | 新しいターゲットを容易に追加可能 |
テストのしやすさ | Health クラスが必要 | IHealth をモックしてテスト可能 |
コードの変更に対する耐性 | 変更に対して脆弱 | インターフェースを通じて柔軟 |
メリット:
- 柔軟性: インターフェースを使うことで、さまざまなターゲットに対応したダメージ処理が簡単に拡張可能です。例えば、シールドやボスキャラクターなど、異なるターゲットに対しても同じミサイルクラスでダメージ処理を行うことができます。
- テストの容易さ:
IHealth
インターフェースを使用しているため、ユニットテストでモックを使うことが容易になり、Missile
クラスの動作を個別にテストできます。 - 変更に強い設計: 依存性逆転を導入することで、クラスの変更が他のクラスに影響を与えにくくなります。たとえば、
Health
クラスを変更しても、Missile
クラスには影響がありません。
結論
依存性逆転を取り入れた設計により、ターゲットにダメージを与
える処理は柔軟かつ拡張可能になり、異なる種類のターゲットにも簡単に対応できるようになります。従来の具体的なクラスに依存した設計では、拡張や変更が困難で、コードの保守性が低下しますが、インターフェースを使った抽象的な設計では、変更や拡張に強いシステムを構築できます。
依存性逆転は、特に大規模なゲームやプロジェクトにおいて、今後の拡張や保守を見据えた重要な設計アプローチです。
この技術資料は、依存性逆転を取り入れた設計と従来の設計を比較し、柔軟性や拡張性、テストのしやすさに焦点を当ててまとめました。
ディスカッション
コメント一覧
まだ、コメントがありません