はじめに
DI の概要
依存性の注入というのは、「Dependency Injection(DI)」の直訳です。パッと見で何のことかが非常にわかりづらいですが、やりたいこととしては「依存性があるプログラムは保守・テストがしづらいので、それを解決する」ということです。
依存性の注入(DI)について解説してみる
では依存性というのは何かというと、よく「プログラムが別のプログラムに依存している状態」を指す言葉として説明されますが、これもまた強烈にわかりづらいですね。
私は「プログラムが別のプログラムの「メソッドの実装やインスタンス変数」に依存している」と解釈して考えています。
DI の例
他クラスへの依存性が強い状態(DI 導入前)
Unity C#
public class Pistol
{
public void Shoot()
{
}
}
public class Shotgun
{
public void Shoot()
{
}
}
public class Player
{
private void OnClickedAttackButton()
{
//プレイヤーの使用する武器をピストルからショットガンに変更する際はこの部分を書き換える必要がある
Pistol pistol = new Pistol();
//プレイヤーの攻撃処理がピストルのみに依存せざるを得ない状態になってしまっている
pistol.Shoot();
}
}
Unreal C++
UCLASS()
class SAMPLE_API APistol : public AActor
{
GENERATED_BODY()
public:
void Shoot();
};
UCLASS()
class SAMPLE_API AShotgun : public AActor
{
GENERATED_BODY()
public:
void Shoot();
};
UCLASS()
class SAMPLE_API APlayer : public ACharacter
{
GENERATED_BODY()
public:
void OnClickedAttackButton()
{
//プレイヤーの使用する武器をピストルからショットガンに変更する際はこの部分を書き換える必要がある
APistol* Pistol = NewObject<APistol>();
//プレイヤーの攻撃処理がピストルのみに依存せざるを得ない状態になってしまっている
Pistol->Shoot();
};
};
他クラスへの依存性が弱い状態(DI 導入後)
Unity C#
public interface IWeapon
{
void Shoot();
}
public class Pistol : IWeapon
{
public void Shoot()
{
}
}
public class Shotgun : IWeapon
{
public void Shoot()
{
}
}
public class Player
{
private IWeapon weapon;
public void SetPlayerWeapon(IWeapon weapon)
{
//インターフェース経由で武器を取得するため
//このクラス内のコードを修正する事なく
//プレイヤーの武器をピストルにするかショットガンにするかを
//外部クラスで決める事が出来る
this.weapon = weapon;
}
private void OnClickedAttackButton()
{
weapon?.Shoot();
}
}
Unreal C++
UINTERFACE(MinimalAPI)
class UWeaponInterface : public UInterface
{
GENERATED_BODY()
};
class SAMPLE_API IWeaponInterface
{
GENERATED_BODY()
public:
virtual void Shoot();
};
UCLASS()
class SAMPLE_API APistol : public AActor, public IWeaponInterface
{
GENERATED_BODY()
public:
virtual void Shoot() override;
};
UCLASS()
class SAMPLE_API AShotgun : public AActor, public IWeaponInterface
{
GENERATED_BODY()
public:
virtual void Shoot() override;
};
UCLASS()
class SAMPLE_API APlayer : public ACharacter
{
GENERATED_BODY()
private:
TScriptInterface<IWeaponInterface> Weapon;
public:
void SetPlayerWeapon(TScriptInterface<IWeaponInterface> WeaponInterface)
{
//インターフェース経由で武器を取得するため
//このクラス内のコードを修正する事なく
//プレイヤーの武器をピストルにするかショットガンにするかを
//外部クラスで決める事が出来る
Weapon = WeaponInterface;
};
void OnClickedAttackButton()
{
if (Weapon != nullptr) Weapon->Shoot();
};
};
DI コンテナ
DI コンテナの概要
コンテナ
ソフトウェア開発におけるコンテナとは、アプリケーション内のオブジェクトの管理をしてくれる入れ物のようなものです。コンテナによって、オブジェクトの管理をコンテナに任せることによって、ユーザーはビジネスロジックの実装に集中することができます。Dockerなどが有名ですね。
Spring BootのDIコンテナについて、概念や実装方法を解説
DI コンテナ
DIコンテナは、アプリケーション内のオブジェクトのライフサイクル(生成から破棄)や依存関係を管理し、必要な依存オブジェクトを適切なタイミングで自動的に注入してくれます。コンテナという広い括りではDockerと同じで入れ物の役割を果たしますが、いくつかの異なる点があります。DockerはOSレベルで動作し、アプリケーション自体をパッケージ化することで、異なる環境でも同じような動作を提供できるのに対して、DIコンテナはアプリケーションのコードレベルで動作し、コードの依存関係を解消するという違いがあります。
Spring BootのDIコンテナについて、概念や実装方法を解説
Unity における DI コンテナ(Extenject / VContainer)
Extenject(Zenject)
【Unity】プロジェクトを作成したらとりあえずインポートするアセット集
依存性注入したいときに必須
パフォーマンスはVContainerの方が優れているので、どうしても使いたい機能がExtenjectにしかない場合を除いてVContainerがオススメ
VContainer
Extenjectが2020年で止まっているのでUnity2020以降で依存性注入したいとき使用する
Unity における DI コンテナの使用例(Extenject)
手順①
こちら(Unity Asset Store)から「Extenject Dependency Injection IOC」をマイアセットに追加して Unity で開く。
手順②
「Window > Package Manager」で「My Assets」に切り替えて「Extenject Dependency Injection IOC」の「Import」を押す。
手順③
新たにインターフェースと、そのインターフェースを実装したクラスを作成する。
public interface IWeapon
{
void Shoot();
}
public class Pistol : IWeapon
{
public void Shoot()
{
}
}
public class Shotgun : IWeapon
{
public void Shoot()
{
}
}
手順④
外部クラスから依存性を注入されたいクラスを作成する。
using UnityEngine;
using Zenject;// Inject 属性を使用するのに必要
public class Player : MonoBehaviour
{
[Inject]//追加
private IWeapon weapon;
private void OnClickedAttackButton()
{
weapon?.Shoot();
}
}
手順⑤
Assets フォルダ内で右クリックして「Create > Zenject」からインストーラーを作成する。
(今回は「Mono Installer」)
手順⑥
作成したインストーラーにコードを記述する。
using Zenject;
public class WeaponInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container
.Bind<IWeapon>() //インターフェースを指定
.To<Pistol>() //上記のインターフェースを実装したクラスを指定
.AsSingle(); //シングル以外もある
}
}
手順⑦
ヒエラルキーウィンドウで右クリックして「Zenject」からコンテキストを作成する。
(今回は「Scene Context」)
Installerで,どこに何をバインドするかを決めたら最後は
[Unity] Zenject入門Context
で,適用範囲を決めます.
プログラムで言うスコープのようなもので,開発プロジェクト全体に及ぼすのか,シーン内だけに及ぼすのか設定できます.
手順⑧
作成した Scene Context のゲームオブジェクトにインストーラーのスクリプトをアタッチしてインスペクターの「Scene Context > Mono Installers」にこのゲームオブジェクト自身を追加する。
その他
参考記事
DI 全般
- 依存性の注入(DI)について解説してみる
- DI・DIコンテナ、ちゃんと理解出来てる・・?
- Unityソルジャーでも分かる、世界を繋げるDIコンテナの話
- 【Unity】プロジェクトを作成したらとりあえずインポートするアセット集