はじめに
UnityでDI Container
といったらZenject
やExtenject
,VContainer
あたりが有名ですよね。
Dependency Injection
パターンを利用する場合は誰がインスタンスを生成・注入するかが難しい問題になりますが、その解決法の一つとしてDI Container
が利用されます。
ただDependency Injection
= DI container
と思われている方もいるようですが、それは正しくありません。
今回はDIコンテナ以外の解決法の一つである、Poor man's dependency injection
について紹介したいと思います。
下準備
以下のようなクラスを考えてみます。
public class RestaurantService { private readonly IGeolocationService _geolocationService; public RestaurantService(IGeolocationService geolocationService) { _geolocationService = geolocationService; } public string GetRestaurant() { var location = _geolocationService.GetCurrentLocation(); // 本来は座標からレストランを色々検索して返す return "レストラン: " + location; } } public interface IGeolocationService { float GetCurrentLocation(); } public class GeolocationService : IGeolocationService { public float GetCurrentLocation() { // 本来はWebAPIを使ったり色々な処理をして座標を返す return 0; } }
こちらのサイトを参考にしながら書きましたが,メソッドのシグネチャは適当です。注目してもらいたいところはRestaurantService
のコンストラクタにてインスタンスを注入する箇所ですね。
Service LocatorとDependency InjectionパターンとDI Container - nuits.jp blog
このように依存するインスタンス(オブジェクト)を外部から注入しているのがDependency Injection
パターンです。
Poor man's dependency injection
このクラス達に対してPoor man's dependency injection
を適応してみます。
public class RestaurantService { private readonly IGeolocationService _geolocationService; // 引数なしのコンストラクタを定義する public RestaurantService() : this(new GeolocationService()){} // テストをする時はこっちのコンストラクタを利用する public RestaurantService(IGeolocationService geolocationService) { _geolocationService = geolocationService; } public string GetRestaurant() { var location = _geolocationService.GetCurrentLocation(); // 本来は座標からレストランを色々検索して返す return "レストラン: " + location; } }
Poor man’s dependency injection
では引数なしのコンストラクタ(厳密には注入したいインスタンスを引数にとらないコンストラクタ)を用意して、そこで依存先のインスタンスを設定してしまいます。
依存先のクラスが基本は変わらない場合にとても有効だと思います。
Dependency Injection
を適応した理由の一つでもあるテスタビリティについてもバッチリなのですが、実践してみましょう。
単体テストを行う
public class RestaurantServiceTest { [Test] public void RestaurantServiceTestSimplePasses() { var restaurantService = new RestaurantService(new GetlocationServiceMock()); Assert.AreEqual("レストラン: 0", restaurantService.GetRestaurant()); } } public class GetlocationServiceMock : IGeolocationService { public float GetCurrentLocation() { // あらかじめ決めた値を毎回返す return 0; } }
まあいつもと変わりませんね。モックを生成してコンストラクタから注入してあげればOKです。
さいごに
考え方も作り方もシンプルなので、かなり有用なパターンだと思います。
こちらを使うか使わないかを状況次第といったところでしょうか。
また以前VContaienr
を始めて触ってみたのですが、こちらもすごく使いやすかったのでよかったらチェックしてみてください。
www.hanachiru-blog.com
ではまた。