依存(Dependency)とは、あるクラスやモジュールが他のクラスやモジュールに対して依存している状態を指します。
依存関係があると、あるコンポーネントが機能するためには他のコンポーネントが必要であることを意味します。
例えば、クラスAがクラスBのインスタンスを使って処理を行う場合、クラスAはクラスBに依存していると言います。これにより、クラスAの機能がクラスBの実装に依存することになります。
graph TD
A[Class A] -->|依存| B[Class B]
ここで、Class A が Class B に依存していることを示しています。つまり、Class A の動作が Class B によって支えられているということです。
public class Engine {
public void Start() {
// エンジンのスタート処理
}
}
public class Car {
private Engine engine;
public Car() {
engine = new Engine(); // 依存を直接インスタンス化
}
public void Drive() {
engine.Start(); // エンジンを使う
}
}
この例では、Car
クラスが Engine
クラスに依存しています。
Car
のコンストラクターで Engine
のインスタンスが直接作成され、Drive
メソッドで Engine
の Start
メソッドが呼ばれています。
Car
クラスのテストを行う際、Engine
クラスの実装に依存してしまうため、テストが複雑になりえます。Car
クラスの変更が Engine
クラスに影響を与える可能性があります。クラス間の結合度が高くなります。依存性の注入(Dependency Injection, DI)は、コンポーネントの依存関係を外部から注入することで、依存関係を解決する方法です。
DIにより、依存関係を直接コード内で作成するのではなく、外部のコンテナやファクトリーを使用して注入します。
これにより、依存関係の管理が簡単になり、テストや保守がしやすくなります。
public class Engine {
public void Start() {
// エンジンのスタート処理
}
}
public interface IEngine {
void Start();
}
public class Car {
private readonly IEngine engine;
// コンストラクターで依存性を注入
public Car(IEngine engine) {
this.engine = engine;
}
public void Drive() {
engine.Start(); // 注入されたエンジンを使用
}
}
上記の例では、Car
クラスは IEngine
インターフェースに依存しており、Engine
クラスの実装は外部から注入されます。これにより、Car
クラスの依存関係が緩くなり、テストや拡張が容易になります。
graph TD
A[Car] -->|依存性注入| B[IEngine]
B --> C[Engine]
依存関係は、クラスやモジュール間の関係を示し、DIはこれらの依存関係を管理する方法です。DIコンテナを使用することで、依存関係の管理が効率的になり、コードのテスト、保守性、拡張性が向上します。
DI(Dependency Injection)コンテナを利用する方法は、MVPやMVCなどのパターンにおいても非常に有効です。DIコンテナを使うことで、依存性の管理が容易になり、コードの再利用性やテストのしやすさが向上します。
依存性注入(DI: Dependency Injection)は、クラスが必要とする他のクラス(依存するクラス)を外部から注入する設計パターンです。DIを使うことで、オブジェクトの依存関係を明確にし、柔軟なコードの設計が可能になります。
DIコンテナは、依存性を自動的に管理・解決してくれるフレームワークです。Unityや他のフレームワークで使われる一般的なDIコンテナとしては、ZenjectやAutofacなどがあります。
UnityのプロジェクトでMVPやMVCを設計する際、DIコンテナを使用することで以下のメリットがあります。
以下は、UnityでZenjectを使ってMVPパターンを実装する例です。
public interface IGameModel {
int Score { get; set; }
void IncreaseScore();
}
public class GameModel : IGameModel {
public int Score { get; set; } = 0;
public void IncreaseScore() {
Score++;
}
}
public interface IGameView {
void UpdateScore(int score);
}
public class GameView : MonoBehaviour, IGameView {
public Text scoreText;
public void UpdateScore(int score) {
scoreText.text = "Score: " + score.ToString();
}
}
public class GamePresenter {
private readonly IGameModel model;
private readonly IGameView view;
public GamePresenter(IGameModel model, IGameView view) {
this.model = model;
this.view = view;
}
public void OnScoreButtonPressed() {
model.IncreaseScore();
view.UpdateScore(model.Score);
}
}
Zenjectを使って、GameModel
と GamePresenter
を GameView
に注入します。
using Zenject;
public class GameInstaller : MonoInstaller {
public override void InstallBindings() {
Container.Bind<IGameModel>().To<GameModel>().AsSingle();
Container.Bind<GamePresenter>().AsTransient();
}
}
GameView
でPresenterを利用します。
using Zenject;
public class GameView : MonoBehaviour, IGameView {
public Text scoreText;
private GamePresenter presenter;
[Inject]
public void Construct(GamePresenter presenter) {
this.presenter = presenter;
}
public void OnScoreButtonClicked() {
presenter.OnScoreButtonPressed();
}
public void UpdateScore(int score) {
scoreText.text = "Score: " + score.ToString();
}
}
GameModel
や GamePresenter
を手動で生成することなく、DIコンテナがそれらを自動的にインジェクトするため、コードの可読性が向上し、バグが減ります。GameModel
のモックを作成して GamePresenter
に注入し、ユニットテストを容易に行うことができます。MVPやMVCといった設計パターンにDIコンテナを組み合わせることで、依存関係の管理がシンプルになり、コードのメンテナンスや拡張が容易になります。特に、テストや変更に強い設計をしたい場合には、DIコンテナの導入が非常に有効です。