nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Development Technologies。

Service LocatorをInjectionするパターンの再考察

しばらく前に、Dependency InjectionパターンとService Locatorパターンの記事を書きました。

www.nuits.jp

この中で、私はService Locatorパターンは次の点でDependency Injectionパターンに劣ると記載しました。

  1. 本来不要であるServiceLocatorへの依存が発生してしまう
  2. 依存関係が分かりにくくなる
  3. テストが困難になる

詳細は先のリンクの記事を参照していただきたいのですが、3.について次のような意見を頂いたため再考してみました。

「Service Locatorを利用するとテストの並行実行性が保てないのは、Service Locatorが静的な実装になっているからで、インスタンスを利用すれば良いのでないか?」

なお本エントリーは、元々先のリンク先の末尾に記載していたものがベースとなっていますが、一旦削除し独立してまとめ直しました。

さて頂いた意見を図示してみると、つまりこういう感じでしょうか?

IServiceLocator serviceLocator = new ServiceLocator();
serviceLocator.Register<IGeolocationService, TokyoGeolocationService>();
var restaurantService = new RestaurantService(serviceLocator);
var result = restaurantService.GetRestaurants();
public class RestaurantService
{
    private readonly IServiceLocator _serviceLocator;
    public RestaurantService(IServiceLocator serviceLocator)
    {
        _serviceLocator = serviceLocator;
    }

    public IList<Restaurant> GetRestaurants()
    {
        var geolocationService = _serviceLocator.Resolve<IGeolocationService>();
        return GetRestaurants(geolocationService.GetCurrentLocation());
    }
    ...
}

このパターンであれば、確かにテストの並行実行性は担保できています。
またService Locatorからもインターフェースを抽出することで、Service Locatorへの強依存もなくなります。

しかし、このパターンはその他の2つの問題を解決できていません。

  1. 本来不要であるServiceLocatorへの依存が発生してしまう
  2. 依存関係が分かりにくくなる

そして、Service Locatorをインジェクションするパターンは次の疑問を新たに生みます。

  • Service LocatorをインジェクションするパターンはDependency Injectionパターンの利点をスポイルしているだけではないか?

本来不要であるServiceLocatorへの依存が発生してしまう

Service LocatorパターンもDependency Injectionパターンも、元々は「関心の分離(Separation of Concern)」を実現するための物でした。
しかし、Service Locatorパターンを利用すると、新たにService Locatorという「関心」が発生してしまいます。
Service Locatorからインターフェースを抽出し、インジェクションすることで、Service Locatorの密結合は防げていますが、依然として「関心」が増えた状態になっていることには違いがありません。

依存関係が分かりにくくなる

Service Locatorの場合、あるインスタンスが内部でどのクラスへ依存しているのか隠ぺいされてしまいます。

public class RestaurantService
{
    public IList<Restaurant> GetRestaurants()
    {
        IGeolocationService geolocationService = ServiceLocator.Resolve<IGeolocationService>();
        return GetRestaurants(geolocationService.GetCurrentLocation());
    }
}

この様にRestaurantServiceでは内部でIGeolocationServiceを利用しているわけですが、RestaurantServiceの利用者にはそれを知りえる余地がありません。
これに対して、Dependency Injectionパターンの場合、次のようになります。

var geolocationService = new GeolocationService();
var restaurantService = new RestaurantService(geolocationService);
var result = restaurantService.GetRestaurants();
public class RestaurantService
{
    private readonly IGeolocationService _geolocationService;

    public RestaurantService(IGeolocationService geolocationService)
    {
        _geolocationService = geolocationService;
    }

    public IList<Restaurant> GetRestaurants()
    {
        return GetRestaurants(_geolocationService.GetCurrentLocation());
    }
}

RestaurantServiceがIGeolocationServiceに依存していることが自明となりますよね。

この点もService Locatorをインジェクションしても解決する事ができず、課題のままとして残ります。

Service LocatorをインジェクションするパターンはDependency Injectionパターンの利点をスポイルしているだけではないか?

そもそもService Locatorをインジェクションするとした場合、Service Locator自体が依存(Dependency)オブジェクトでしょう。
であれば最初から直接利用するオブジェクトをインジェクションせず、Service Locatorをインジェクションし間接的に対象オブジェクトを取得する事にはなんのメリットがあるのでしょうか?
私にはちょっとそのメリットが見えてきませんでした。

思考ゲームとして非常に有意義な議題でしたが、実用的にはやはりService LocatorパターンよりはDependency Injectionパターンの方が良いのではないか?というのが私の現時点での結論です。