課題6: 依存性の逆転を取り入れて BasketController の肥大化を防ぐ
依存性の逆転の原則を適用し、BasketController
がアイテムの種類に依存しない設計に変更することで、コードの拡張性と保守性を向上させる。
ステップ:
アイテムごとの共通の動作を定義するインターフェースを作成します。これにより、BasketController
は具体的なアイテムクラスに依存せず、インターフェースを介してアイテムとやり取りします。
インターフェースの定義:
Collectible: 収集可能
// ICollectible.cs
using UnityEngine;
public interface ICollectible
{
void Collect(); // アイテム収集時の処理
}
ItemController
の修正:
ICollectible
インターフェースを実装し、サウンド再生とスコア変更を行うメソッドを定義します。
GameDirectorオブジェクトのGameDirectorコンポーネントのpoint(得点)に加算します
(これまでは、GetApple()メソッドなどで処理していました)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemController : MonoBehaviour, ICollectible
{
public float dropSpeed = -0.03f;
public AudioClip itemSE; // 各アイテムが持つサウンド
public int scoreChange = 0; // 各アイテムが持つポイント
GameObject director;
void Awake()
{
this.director = GameObject.Find("GameDirector");
}
protected virtual void Start()
{
// 共通の初期化処理をここに記述
}
void Update()
{
// アイテムの移動
transform.Translate(0, this.dropSpeed, 0);
// アイテムが画面外に出たら破壊
if (transform.position.y < -1.0f)
{
Destroy(gameObject);
}
}
public virtual void Collect()
{
// SoundManagerを使用してサウンドを再生
SoundManager.instance.PlaySound(this.itemSE, transform.position);
director.GetComponent<GameDirector>().point += scoreChange;
Destroy(gameObject);
}
}
説明:
ICollectible
インターフェースを実装することで、BasketController
がアイテムの具体的なクラスに依存せずにCollect
メソッドを呼び出せるようになります。- directorが持つ(管理する)得点に加減算されるようにします
3. アイテムごとのクラス作成
- 各アイテムごとにクラスを作成し、
ICollectible
インターフェースを実装します。これにより、各アイテムの固有の動作を定義できます。
例1: 爆弾アイテム
public class BombController : ItemController
{
protected override void Start()
{
base.Start(); // 基底クラスのStartを呼び出す
this.scoreChange = -50; // 爆弾はスコアを減少させる
}
public override void Collect()
{
base.Collect();
// 爆弾特有の追加処理があればここに記述
}
}
例2: ゴールドアップルアイテム
using UnityEngine;
public class GoldAppleController : ItemController
{
protected override void Start()
{
base.Start(); // 基底クラスのStartを呼び出す
this.scoreChange = 500; // ゴールドアップルはスコアを大幅に増加させる
}
public override void Collect()
{
base.Collect();
// ゴールドアップル特有の追加処理があればここに記述
}
}
残りも同様に作成します
爆弾アイテムの登録サンプル
得点が半分になるのではなく、50点の減点になるように仕様を変えています
4. BasketController
の修正
BasketController
では、アイテムの種類に依存せず、ICollectible
インターフェースを介してアイテムとやり取りします。
目的:
BasketController
が具体的なアイテムクラスに依存せず、ICollectible
インターフェースを介してアイテムとやり取りできるようにする。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BasketController : MonoBehaviour
{
void Start()
{
Application.targetFrameRate = 60;
}
void OnTriggerEnter(Collider other)
{
// ICollectibleインターフェースを実装しているか確認
ICollectible collectible = other.gameObject.GetComponent<ICollectible>();
if (collectible != null)
{
collectible.Collect();
}
}
void Update()
{
float moveDistance = 1.0f; // 移動する距離(1マス)
Vector3 newPosition = transform.position;
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
newPosition += Vector3.left * moveDistance; // 左に1マス移動
}
if (Input.GetKeyDown(KeyCode.RightArrow))
{
newPosition += Vector3.right * moveDistance; // 右に1マス移動
}
if (Input.GetKeyDown(KeyCode.UpArrow))
{
newPosition += Vector3.forward * moveDistance; // 前(上)に1マス移動
}
if (Input.GetKeyDown(KeyCode.DownArrow))
{
newPosition += Vector3.back * moveDistance; // 後ろ(下)に1マス移動
}
// グリッド範囲内に制限(例: x, z が -1, 0, 1 の範囲)
newPosition.x = Mathf.Clamp(Mathf.Round(newPosition.x), -1.0f, 1.0f);
newPosition.z = Mathf.Clamp(Mathf.Round(newPosition.z), -1.0f, 1.0f);
// バスケットの位置を更新
transform.position = new Vector3(
Mathf.Round(newPosition.x),
transform.position.y,
Mathf.Round(newPosition.z)
);
}
}
コードの調整:
BasketController
がアイテムの種類に依存しないため、GetApple()
やGetBomb()
といった具体的なメソッド呼び出しを排除し、イベント駆動設計へと移行します。
テストと確認:
- ゲームをプレイし、バスケットが各アイテムに衝突した際に、各アイテムが正しくサウンドを再生し、スコアが適切に変更されることを確認する。
学習ポイント:
- オブジェクト指向設計の基本概念。
- インターフェースの定義と実装方法。
- 依存性の逆転の原則(Dependency Inversion Principle)の理解と適用。
依存性逆転の原則(DIP:Dependency Inversion Principle)とは?
依存性逆転の原則(DIP)は、SOLID原則の一つであり、以下の2つのルールから構成されます:
- 高レベルモジュールは低レベルモジュールに依存すべきではない。 両者は抽象(インターフェースや抽象クラス)に依存すべきである。
- 抽象は詳細に依存すべきではない。 詳細は抽象に依存すべきである。
この原則を適用することで、システムの柔軟性、拡張性、保守性が向上します。
リファクタリング前のクラス図
リファクタリング前のクラス図では、BasketController
が GameDirector
を介して ItemController
に依存しています。具体的なアイテム処理が高レベルモジュール(BasketController
)から低レベルモジュール(ItemController
)に直接依存しているため、DIPに違反しています。
リファクタリング前のクラス図の特徴
BasketController
OnTriggerEnter
メソッド内でアイテムのタグをチェックし、GameDirector
のGetApple()
やGetBomb()
を呼び出しています。
- DIPへの影響
- 高レベルモジュール(
BasketController
)が低レベルモジュール(ItemController
)に依存しています。 - 具体的なアイテム処理が
BasketController
に組み込まれているため、新しいアイテムを追加する際にBasketController
を変更する必要があります。 - これはDIPに違反し、システムの柔軟性と拡張性を低下させます。
- 高レベルモジュール(
リファクタリング後のクラス図(DIP適用)
リファクタリング後のクラス図では、ICollectible
インターフェースを導入し、BasketController
が具体的なアイテムクラスに依存するのではなく、抽象に依存するように設計を変更しました。これにより、DIPが遵守され、システムの柔軟性と拡張性が向上しています。
リファクタリング後のクラス図の特徴
ICollectible
インターフェースCollect()
メソッドを定義し、アイテム収集時の共通処理を標準化しています。- 高レベルモジュール(
BasketController
)と低レベルモジュール(ItemController
およびその派生クラス)がこのインターフェースに依存します。
ItemController
クラスと派生クラスItemController
がICollectible
を実装し、基本的なアイテムの挙動(Collect()
メソッド)を提供します。BombController
やGoldAppleController
はItemController
を継承し、Collect()
メソッドをオーバーライドして各アイテム特有の処理を実装します。
BasketController
クラスICollectible
インターフェースに依存するようになり、具体的なアイテムクラスには依存しません。- これにより、新しいアイテムタイプを追加する際に
BasketController
を変更する必要がなくなります。
ItemGenerator
クラス- 生成するアイテムの型として
ICollectible
を使用し、具体的なアイテムクラスに依存しない設計に変更しました。 - 新しいアイテムタイプを追加する際にも、
ItemGenerator
の変更は最小限に抑えられます。
- 生成するアイテムの型として
- DIPへの影響
- 高レベルモジュール(
BasketController
)が低レベルモジュール(具体的なアイテムクラス)に依存しなくなり、抽象(ICollectible
)に依存するようになりました。 - 低レベルモジュール(具体的なアイテムクラス)が抽象(
ICollectible
)に依存するようになりました。
- 高レベルモジュール(
リファクタリング前後のクラス図比較による学習ポイント
リファクタリング前
- 高レベルモジュールが低レベルモジュールに依存
BasketController
がItemController
に依存しているため、具体的なアイテム処理に強く結合しています。- 新しいアイテムを追加する際に
BasketController
やGameDirector
の変更が必要になる可能性があります。
- 拡張性の低さ
- 新しいアイテムタイプを追加する際に既存の高レベルモジュールに影響を与えるため、システム全体の拡張が困難です。
リファクタリング後
- 高レベルモジュールが抽象に依存
BasketController
がICollectible
インターフェースに依存することで、具体的なアイテムクラスには依存しなくなりました。- 新しいアイテムを追加する際に
BasketController
やGameDirector
の変更が不要です。
- 低レベルモジュールが抽象に依存
- 具体的なアイテムクラス(
BombController
,GoldAppleController
)がICollectible
インターフェースを実装することで、抽象に依存しています。 - これにより、具体的な実装の変更が高レベルモジュールに影響を与えません。
- 具体的なアイテムクラス(
- 拡張性と保守性の向上
- 新しいアイテムタイプを追加する際に、
ICollectible
を実装する新しいクラスを作成するだけで済み、システムの拡張が容易になります。 - モジュール間の結合度が低くなるため、保守性が向上します。
- 新しいアイテムタイプを追加する際に、
学習ポイント
- 依存性逆転の原則(DIP)の重要性
- 高レベルモジュールが低レベルモジュールに直接依存する設計は避け、抽象に依存することでシステムの柔軟性と拡張性を高める。
- インターフェースの活用
- インターフェースを用いることで、具体的な実装に依存せずにモジュール間の相互作用を設計できる。
- ポリモーフィズムの利点
- 派生クラスが基底クラスやインターフェースを実装することで、異なるアイテムタイプ間の共通の動作を統一的に扱える。
8. まとめ
このリファクタリングを通じて、依存性逆転の原則(DIP)を適用することで、BasketController
と ItemController
間の依存関係が抽象に基づくものへと変わり、システム全体の柔軟性と拡張性が向上しました。具体的には以下の点が改善されました:
- 高レベルモジュールが低レベルモジュールに直接依存しなくなった。
- 新しいアイテムタイプの追加が容易になり、既存のコードに影響を与えない。
- モジュール間の結合度が低くなり、保守性とテスト容易性が向上。
オブジェクト指向設計の原則を適用することで、コードベースの品質と可読性を高め、長期的なプロジェクトの成功に寄与します。今後もSOLID原則を念頭に置いた設計を心がけることで、より堅牢で拡張性の高いソフトウェア開発が可能となるでしょう。
ディスカッション
コメント一覧
まだ、コメントがありません