DIコンテナの非推奨な使い方であるサービスロケーターパターンについてまとめました。
はじめに
この記事ではDIコンテナの非推奨な使い方であるサービスロケーターパターンについて説明します。
用語や実装例などはMicrosoft.Extensions.DependencyInjectionのものに準拠します(内容としてはDIコンテナ全般に共通する話です)。
Microsoft.Extensions.DependencyInjectionの使い方は以下の記事にまとめていますので必要に応じて参照してください。
良い例
さてDIコンテナに登録するサービスは、依存するサービスのインターフェースをコンストラクタで受け取ります。
(コンストラクタ以外の注入手段もありますが本筋ではないので割愛します。基本はコンストラクタ。)
public class ExampleService { private readonly IFooService _fooService; // 依存関係にあるサービスをコンストラクタで注入 public ExampleService(IFooService fooService) { _fooService = fooService; } }
そしてこの依存関係をDIコンテナにより解消します(この部分の実装は割愛、関連記事を参照)。
これがDIコンテナの良い使い方です。
良くない例 - サービスロケーターパターン
このようにDIコンテナは便利ですが、以下のような使い方もできてしまいます。
using Microsoft.Extensions.DependencyInjection; public class ExampleService { private readonly IFooService _fooService; // ※良くない例 // コンストラクタでIServiceProviderを注入 public ExampleService(IServiceProvider serviceProvider) { _fooService = serviceProvider.GetService<IFooService>(); } }
コンストラクタでサービスではなくIServiceProvider
を受け取り、そこからサービスを取得しています。
この実装にはコンストラクタ引数で依存関係が把握できないという大きな問題があります。
このクラスを初めて使う人にとって、このクラスを動かすためにIServiceProvider
が必要なことはわかりますが、
そのIServiceProvider
に実際どんなサービスを登録しておけばいいのかは実装を見ないとわかりません。
また必要なサービスが追加されてもコンストラクタは変わらずコンパイルエラーにならないため、保守性に欠けます。
もちろんテスト時にも正しく初期化されたIServiceProvider
が必要になり、正しくメンテしないとテストがこけます。
このような実装はサービスロケーターパターンと呼ばれ、DIの実装としてはやるべきではないとされています。
Microsoft.Extensions.DependencyInjectionとしても非推奨
サービスロケーターパターンはMicrosoft.Extensions.DependencyInjectionのドキュメントにも非推奨であることが書かれています。
本記事でもこれに合わせて「非推奨」という表現にしています。
サービスロケーターパターンをアンチパターンとする主張もみますが、
DIというより制御の反転(IoC)を語る文脈ではアンチパターンと言えるかと思います。