9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Unityでの依存性の注入は世間のそれとは意味が違う

Last updated at Posted at 2023-01-25

独自の発展をしている部分があって、Unity 向けのサンプルや解説を見ると逆に DI が分からなくなる問題。原因は大きく分けて3つ。

その1

[Inject] という Unity 向け DI コンテナにしか存在しない特殊な記法と、一般的な DI を混ぜて話をしている。

  • この特殊記法は便利な一方でちょっと怖い。使える場面でも使ってないなーという例も見る。警戒すべき??

    • DI とは関係ない、DI コンテナの機能なのでコンテナに対する依存度が上がる。
    • 依存性の解決を一か所で行うことでクラス間の依存関係を一覧できる、という DI の恩恵を受けられない。
    • よくわかんないけどなんか動いてる! を助長する DI とは真逆の概念。書き捨てのサンプルなら便利だが、所かまわずやられると依存関係を追うのが大変になる。

その2

界隈でとある本が流行って、DI という手段を目的にするような記事が増えた時期があった。

  • それらの記事に今触れると、話の趣旨(DI という手段を目的にしている)が掴めない。
  • 同時に示される教科書的な目的、得られる結果だけを見ると、「もうやってるが?? それに実現するなら他にも方法あるくない? 余計なおまじない増やす意味は?? なんで?? DI 分からん」となる。

その3

DI コンテナを使ってない。コンストラクター経由で依存性を注入していない。DI やってない??

  • 各種サービス、OS、特定の何かに決め打ちで依存するのはまずいし怖いってことで、抽象化(≒依存性の逆転)して DI やってる・出来る状態になってる事の方が多い。

  • DI・DIコンテナがコンストラクターを使って依存性を注入します。になってるのは、この手法をオブジェクト指向のプログラミング言語向けに一般化しようとしたらコンストラクターだよな、絶対存在するし。てところから(だと思う)

    • コンストラクターで注入するロジックであれば開発言語や DI コンテナに依存しないコードになる。
    • DI コンテナを別のものに変えたり、DI コンテナそのものの利用を中止しても最小限の修正で動く。
    • ところが Unity では MonoBehaviour が特殊なクラスでコンストラクターを使えないため、独自の発展をするに至った。

--

コンストラクターで「自分の側で定義したインターフェイス」を受け取ろう、という手段が DI。目的は「制御の反転」(inversion of control) 参考 →

Unity での DI は今までシングルトンとか使ってた部分を代替するモノ。ぐらいのイメージ。
  • Unity で「DI」を行う意味はない。例もほぼ見かけない。1
  • Unity で「DIコンテナ」を使う意味はあるし、DI 以外の便利機能もある非常に便利なライブラリ。
    • 依存性の逆転は行っている [ App → IService ] ← MyService:IService
    • が、ハードコードで依存 public App() { m_service = new MyService(); }
    • 外部から変更できるように App.Singleton.SetService(new ExternalService());
    • これも依存性の注入と言えるので、そういう意味では行う意味はあるし実は既に DI コンテナ無しで DI を行っている状態。ただ、Unity 絡みで DI と言われた場合は 👆 の事を指しているのか 👇 の事を指しているのかは見極めが必要。

「DIコンテナ」が便利だから使われていて「DIコンテナ」の事を「DI」と呼んでいるだけ、Unity 以外で使われている意味での DI、依存性の注入はマジで一切、何の関係もない。

今までのおまじないを忘れて [Inject] って書くだけで良くなるらしいぞ! このぐらいの捉え方で良い。

int m_value;                // 値型は 0 が勝手に入ってる。
[Inject] Server m_server;   // クラスは null、、、じゃなくて勝手にインスタンスが入ってる!!

void OnRequestReceived() {
    m_server.DoIt();
}

※ チラチラ見てると Unity でメソッドに [Inject] してる例はあんまり、、、無くはないかな?? ぐらい。

MessagePipe というなんも考えずにメッセージ・イベントの送受信ができるライブラリも、今見てみると 「[Inject] のおかげでなんか勝手にインスタンス入ってる!!」 を徹底活用したモノだと分かる。BuiltinContainerBuilder + GlobalMessagePipe に問い合わせる手法の方が処理の流れが明確で良さそうだけど。

そして、DI を行うためのライブラリで、加えて DI 以外のことも出来るおまけが付いてるのが DI コンテナ。[Inject] はオマケ部分

Unity 向けのサンプルや説明は DI ではなく「DIコンテナ」を使って「開発を楽にする」方法を示している場合が多く、混乱が生じる。DIコンテナのオマケで悩んだ結果 → static abstract

Unity と DI コンテナ

Unity 的には、「DIコンテナ」に付いていたオマケ部分の [Inject] がスゴイ便利でうおーーっとなった。その背景には MonoBehaviour の存在がある。

そもそもこの [Inject] というアトリビュートを使った手法、調べた限りでは Unity 向けの DI コンテナにしか存在しない。

MonoBehaviour

Unity といえば MonoBehaviour。こいつはコンストラクタを持たず(持てるけど)、近いものとして Awake() があるが、これは引数を受けない。

DI を実現しようとした場合も、DI と関係ない場合も、

  • GameObject にスクリプトを付ける。
  • コンストラクターへの引数の代わりとしてインスペクターで必要なフィールドを設定する。
    • シーンが小分けでオブジェクトが存在しない等、GUI からは出来ない可能性がある。
    • そもそも GUI で設定が面倒。
  • C# で制御しようとするとこれもまた難しい。
    • いつ、どこで?
    • DontDestroyOnLoad な GameObject に持たせる?
    • 常時監視して該当するスクリプトのフィールドを埋める?
    • シーンロードを担当するクラスが読み込んだ後に設定する?
    • あいつが先にロードされていないとダメ。
    • ライフサイクルの管理は?

内容は単純だけど、見通しが悪くどうするか結構大変。散らかりがち。

一方DIコンテナはDI実現のために気を利かせて [Inject] を作った。そしてこれがこの問題に見事にハマった。

糖衣構文としての [Inject]

[Inject] これはDIコンテナの機能の一部だけど、DIの概念とは何の関係もない、単なる糖衣構文と言っても差し支えないモノ。特にクラスのフィールドにインスタンスを埋める使い方。

この糖衣構文は Unity MonoBehaviour にまつわる様々な面倒事を [Inject]、と付けるだけで全て肩代わりしてくれるという優れモノ。(大抵はフィールドにシングルトンのインスタンスを設定する、という機能として使われてない??)

加えてライフサイクルの管理までしてくれる。

別に依存性がどうこう、インターフェイスを誰が定義したかとか関係ない。依存性じゃなくてインスタンスを注入する手段として、便利に使われている。

インスタンス&ライフサイクル管理ライブラリ

結果、Unity 向けDIコンテナはもちろん DI にも使える、が、MonoBehaviour インスタンス&ライフサイクル管理ライブラリとして独自の発展を遂げ、DI! Inject! 依存性の注入! と言ったら大抵は糖衣構文の方を意味してると思ってほぼ差し支えない情勢。だと思う。

調べるきっかけになったプロジェクトも DI と捉えず、ライフサイクル管理とインスタンスの取得にDIコンテナを使ってるな?? として見るとすんなり理解できた。

昔→シングルトン。今→DIコンテナ。

DI はインターフェイスで受ける! とかもなくて、マッピングする術としてココはクラスじゃなくてインターフェイスだなー、ぐらいの感覚で使ってる。

あと DI でテストが楽に~~というのも結果的についてくる副産物で目的ではさそう。それを実現する手段なら探せば他にもたくさんあるだろう。

--

依存性の注入と C# 11 を投稿したら Qiita が色々とおススメに出してくれました。

Unity ユーザー的にはインフラへの依存を(クラスの定義的な意味で)避けるって言われても、いや有名どころのライブラリ読み込んで抽象化する位で十分なんですが、という感じですよね。

MonoBehaviour のライフサイクルを考えないでも全部やってくれるのは便利だし、今後はDIコンテナを旨いこと使っていきたい。

以上です。お疲れ様でした。

  1. 言い過ぎ

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?