DIコンテナの非推奨な使い方・サービスロケーターパターン

DIコンテナの非推奨な使い方であるサービスロケーターパターンについてまとめました。

はじめに

この記事ではDIコンテナの非推奨な使い方であるサービスロケーターパターンについて説明します。
用語や実装例などはMicrosoft.Extensions.DependencyInjectionのものに準拠します(内容としてはDIコンテナ全般に共通する話です)。

www.nuget.org

Microsoft.Extensions.DependencyInjectionの使い方は以下の記事にまとめていますので必要に応じて参照してください。

light11.hatenadiary.com

良い例

さて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のドキュメントにも非推奨であることが書かれています。

docs.microsoft.com

本記事でもこれに合わせて「非推奨」という表現にしています。
サービスロケーターパターンをアンチパターンとする主張もみますが、
DIというより制御の反転(IoC)を語る文脈ではアンチパターンと言えるかと思います。

関連

light11.hatenadiary.com

参考

www.nuget.org

docs.microsoft.com