依存性逆転原則学習:BasketControllerを使ったダメージ処理設計ガイド
この資料では、Unityで落下してくるりんご(Apple)や爆弾(Bomb)をバスケットで受け止めた際にHPを減少させる処理を、まずはシンプルなタグ判定による実装例で紹介し、その後「依存性逆転の法則(DIP: Dependency Inversion Principle)」を適用したリファクタリング例へとステップアップしていきます。最後に、初学者にも分かりやすいようにDIPの有効性を解説し、学習のポイントを整理します。
目次
1. シンプルなタグ判定による HP デクリメント
BasketController.cs
using UnityEngine;
public class BasketController : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
// 「Apple」タグのオブジェクトに当たったら HP をデクリメント
if (other.CompareTag("Apple"))
{
var apple = other.GetComponent<Apple>();
if (apple != null)
{
apple.HP--;
}
}
// 「Bomb」タグのオブジェクトに当たったら HP をデクリメント
else if (other.CompareTag("Bomb"))
{
var bomb = other.GetComponent<Bomb>();
if (bomb != null)
{
bomb.HP--;
}
}
}
}
Apple.cs
using UnityEngine;
public class Apple : MonoBehaviour
{
public int HP = 3;
}
Bomb.cs
using UnityEngine;
public class Bomb : MonoBehaviour
{
public int HP = 1;
}
- ポイント
- CompareTag でタグを判定し、それぞれのクラスの HP フィールドを直接操作している。
- 新たにダメージ対象を増やすたびに if–else を追加する必要があるため、クラスが増えるほど可読性・保守性が低下する。
- 高レベルモジュール(BasketController)が低レベルモジュール(Apple/Bomb)に直接依存している。
2. IDamageable インターフェースの定義
/// <summary>
/// ダメージを与える抽象を表すインターフェース
/// </summary>
public interface IDamageable
{
/// <summary>
/// ダメージを与える処理。amount ぶん HP を減らす。
/// </summary>
void TakeDamage(int amount);
}
- ポイント
- 「HPを持つものは何でもダメージを与えられる」という共通の契約(抽象)を定義。
- 高レベルモジュールはこの抽象にのみ依存し、具体実装には依存しない設計を目指す。
3. 依存性逆転の法則を適用した実装
Apple.cs
using UnityEngine;
public class Apple : MonoBehaviour, IDamageable
{
public int HP = 3;
public void TakeDamage(int amount)
{
HP -= amount;
if (HP <= 0)
Destroy(gameObject);
}
}
Bomb.cs
using UnityEngine;
public class Bomb : MonoBehaviour, IDamageable
{
public int HP = 1;
public void TakeDamage(int amount)
{
HP -= amount;
if (HP <= 0)
Explode();
}
private void Explode()
{
// 爆発エフェクト再生など
Destroy(gameObject);
}
}
BasketController.cs
using UnityEngine;
public class BasketController : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
// IDamageable を実装しているものを探し、一律にダメージを与える
var damageable = other.GetComponent<IDamageable>();
if (damageable != null)
{
damageable.TakeDamage(1);
}
}
}
- ポイント
- BasketController は具体クラス(Apple/Bomb)を参照せず、IDamageable インターフェースだけに依存。
- 各ダメージ対象(Apple/Bomb)は IDamageable を実装しており、TakeDamage メソッドで自身の HP を管理する。
- 新規ダメージ対象(例:Box や Missile)を追加する際は、IDamageable を実装したクラスを作成するだけでよく、コントローラー側に変更を加える必要がない。
4. 依存性逆転の法則の有効性
- 高レベル/低レベルの分離
- 高レベルモジュール(BasketController)は抽象(IDamageable)にのみ依存し、低レベルモジュール(Apple/Bomb)はその抽象を実装するだけ。
- これにより、クラス同士の結びつきが弱くなり、システム全体の柔軟性が向上する。
- 拡張性の向上
- 例:新しく「Box」や「Missile」といったダメージ対象を追加する場合、以下の流れで対応可能。
- Box クラスを作成し、IDamageable を実装
- Box の TakeDamage メソッド内で HP 管理や破壊処理を記述
- Unity エディタ上で Box オブジェクトにタグやコライダーを設定
- 既存の BasketController はそのままで、動作できる
- 既存コードを変更しないため、修正コストを大幅に削減できる。
- 例:新しく「Box」や「Missile」といったダメージ対象を追加する場合、以下の流れで対応可能。
- 単体テストが容易
- BasketController の単体テストでは、モック実装の IDamageable を用意して、意図した回数だけ TakeDamage が呼ばれるかを検証できる。
- 具体クラス(Apple/Bomb)を差し替えずにテストできるため、テストコードがシンプルになる。
- 可読性・保守性の向上
- 従来の if–else 分岐がなくなり、OnTriggerEnter の処理は「衝突した相手がダメージ可能かを判定し、ダメージを与える」という1行のロジックで書ける。
- コードから意図が直感的にわかるため、後から読む人にも理解しやすい。
- 学習上のポイント
- 抽象レイヤーを作る意味:抽象(インターフェース)を介在させることで、高レベルと低レベルの依存を切り離せる。
- 依存性逆転の考え方:上位のモジュールが下位のモジュールに直接依存するのではなく、両方が同じ抽象(インターフェース)に依存することで結合度を下げる。
- 実践への応用:ダメージ処理以外でも、例として「武器切り替え」「スコア管理」「エフェクト再生」など、複数の実装が考えられる機能で同様に インターフェース+DIP のパターンを適用すると、拡張性やテスト容易性が向上する。
まとめ
- タグ判定による実装 は手軽だが、対象が増えるほど if–else 分岐が増え、保守性・可読性が低下する。
- 依存性逆転の法則(DIP) を適用すると、上位モジュールはインターフェースに依存し、下位モジュールはそのインターフェースを実装するだけ。
- 拡張時やテスト時に変更が少なく、コード品質が向上する。
- 実際の開発では、ダメージ処理以外にもこの考え方を積極的に取り入れてみることで、より柔軟で管理しやすいアーキテクチャを学習できる。
以上が「依存性逆転原則学習」を意識した設計ガイドです。いきなりすべてを理解するのは難しいかもしれませんが、「インターフェースを使って、高レベルな処理と低レベルな処理を切り離す」という考え方を意識して実装してみてください。
訪問数 27 回, 今日の訪問数 1回
ディスカッション
コメント一覧
まだ、コメントがありません