1. ホーム
  2. design-patterns

[解決済み] ServiceLocatorはアンチパターンか?

2022-05-29 20:41:35

質問

最近読んだ本 Mark Seemannの記事 を読みました。

著者は、ServiceLocatorがアンチパターンである2つの主な理由を指摘しています。

  1. APIの使用に関する問題 (これは全く問題ないのですが)

    クラスがサービスロケーターを使用する場合、ほとんどの場合、クラスは1つのPARAMETERLESSコンストラクタしか持っていないので、その依存性を確認することは非常に困難です。 ServiceLocator とは対照的に、DI のアプローチではコンストラクタのパラメータによって依存関係が明示的に示されるため、IntelliSense で依存関係を簡単に確認することができます。

  2. メンテナンスの問題 (これは私を困惑させる)

    次のような例を考えてみましょう。

私たちはクラス 'MyType' というクラスがあり、これはサービスロケータのアプローチを採用しています。

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
    }
}

ここで、クラス 'MyType' に別の依存関係を追加したいと思います。

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
            
        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

そして、ここからが私の誤解の始まりです。著者はこう言っています。

破壊的な変更を導入しているかどうかを判断するのは、かなり難しくなります。サービスロケータが使用されているアプリケーション全体を理解する必要があり、コンパイラはあなたを助けるつもりはありません。

しかし、ちょっと待ってください。もし私たちがDIアプローチを使用していたなら、(コンストラクタ注入の場合)コンストラクタの別のパラメータで依存関係を導入することになります。そして、問題はまだそこにあるのです。もしServiceLocatorをセットアップするのを忘れたら、IoCコンテナに新しいマッピングを追加するのを忘れるかもしれませんし、DIアプローチでも同じ実行時問題を抱えることになります。

また、著者はユニットテストの困難さについて述べています。しかし、DIのアプローチでも問題はないのでしょうか?そのクラスをインスタンス化していたすべてのテストを更新する必要があるのではないでしょうか?私たちのテストをコンパイル可能にするために、新しいモック化された依存関係を渡すように更新することになります。そして、私はその更新と時間の浪費から何の利益も得られないと思います。

Service Locatorのアプローチを擁護するつもりはありません。しかし、この誤解は、私が非常に重要な何かを失っていると思わせます。どなたか、私の疑念を払拭していただけないでしょうか。

を更新しました(要約)。

Service Locator はアンチパターンなのか」という私の疑問に対する答えは、本当に状況によって異なります。そして、私は間違いなく、ツール リストからそれを除外することをお勧めしません。レガシーコードを扱い始めると、非常に便利になるかもしれません。もし、あなたが幸運にもプロジェクトの一番最初の段階であるなら、DIアプローチはService Locatorよりもいくつかの利点があるので、より良い選択かもしれません。

そして、私が新しいプロジェクトでService Locatorを使用しないことを確信した主な違いは以下の通りです。

  • 最も明白かつ重要:Service Locator はクラスの依存関係を隠します。
  • IoC コンテナを利用している場合、起動時にすべてのコンストラクタをスキャンしてすべての依存関係を検証し、マッピングの欠落(または誤った設定)に関して即座にフィードバックします。

詳細については、以下に記載されている優れた回答をお読みください。

どのように解決するのですか?

パターンが適合しない状況があるというだけで、パターンをアンチパターンと定義するならば、それはアンチパターンであると言えます。しかし、その理由では、すべてのパターンがアンチパターンになってしまいます。

その代わりに、私たちはパターンの有効な使い方があるかどうかを見なければなりません。しかし、あなたが挙げた例を見ることから始めましょう。

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

そのクラスのメンテナンスの悪夢は、依存関係が隠されていることです。そのクラスを作成し使用すると

var myType = new MyType();
myType.MyMethod();

サービスロケーションを使って隠してしまうと、依存関係があることを理解できません。では、代わりに依存性注入を使うとしたら。

public class MyType
{
    public MyType(IDep1 dep1, IDep2 dep2)
    {
    }

    public void MyMethod()
    {
        dep1.DoSomething();

        // new dependency
        dep2.DoSomething();
    }
}

依存関係を直接見つけることができ、依存関係を満たす前にクラスを使用することはできません。

典型的な業務系アプリケーションでは、まさにその理由でサービスロケーションの使用を避けるべきです。他の選択肢がないときに使用するパターンであるべきです。

このパターンはアンチパターンなのでしょうか?

いいえ。

たとえば、制御コンテナの反転は、サービスロケーションがなければ機能しません。それは、サービスを内部で解決する方法です。

しかし、もっと良い例は、ASP.NET MVCとWebApiです。コントローラで依存性注入を可能にするのは何だと思いますか?その通りです--サービスの位置です。

あなたの質問

<ブロッククオート

しかし、もしDIのアプローチを使うのであれば、コンストラクタに別のパラメータで依存関係を導入することになります。 コンストラクタの別のパラメータとの依存関係を導入します(コンストラクタ注入の場合)。 コンストラクタ注入の場合)。そして、問題はまだそこにあるのです。

さらに2つの深刻な問題があります。

  1. サービス ロケーションを使用すると、別の依存関係も追加されます。サービス ロケータです。
  2. 依存関係が持つべき寿命と、いつどのようにクリーンアップされるかをどのように伝えるのですか?

コンテナを使用したコンストラクタ注入では、無料でそれを得ることができます。

もし私たちが ServiceLocatorのセットアップを忘れると、IoCコンテナに新しいマッピングを追加するのを忘れるかもしれません。 IoCコンテナに新しいマッピングを追加し忘れるかもしれません。 を実行することになります。

その通りです。しかし、コンストラクタ注入を使えば、どの依存関係が欠けているのかを把握するためにクラス全体をスキャンする必要はありません。

また、より優れたコンテナでは、起動時に(すべてのコンストラクタをスキャンして)すべての依存性を検証するものもあります。そのため、それらのコンテナでは、後の時点ではなく、直接実行時エラーが発生します。

また、著者はユニットテストの困難さについて言及しました。しかし、DIのアプローチにも問題はないのでしょうか?

静的なサービスロケータに依存しないので、問題ありません。静的な依存関係で並列テストを動作させることを試したことがありますか?それは楽しいことではありません。