ServiceLocatorはアンチパターンですか?


137

最近、Service Locatorのアンチパターンに関するMark Seemannの記事を読みました。

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

  1. APIの使用に関する問題(これで問題
    ありません)クラスがサービスロケーターを使用する場合、ほとんどの場合、クラスにはPARAMETERLESSコンストラクターが1つしかないため、依存関係を確認することは非常に困難です。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();
    }
}

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

重大な変更を導入しているかどうかを判断するのは非常に難しくなります。Service Locatorが使用されているアプリケーション全体を理解する必要がありますが、コンパイラーは役に立ちません。

しかし、少し待ってください。DIアプローチを使用している場合は、コンストラクターに別のパラメーターとの依存関係を導入します(コンストラクターインジェクションの場合)。そして、問題はまだそこにあります。ServiceLocatorのセットアップを忘れた場合は、IoCコンテナーに新しいマッピングを追加し忘れた可能性があり、DIアプローチでも同じランタイム問題が発生します。

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

私はService Locatorのアプローチを擁護するつもりはありません。しかし、この誤解により、私は非常に重要な何かを失っていると思います。誰かが私の疑問を払拭できますか?

更新(要約):

「Service Locatorはアンチパターンですか?」という私の質問に対する答えは、実際には状況によって異なります。そして、私はあなたのツールリストからそれを取り除くことを絶対に勧めません。レガシーコードの処理を開始すると、非常に便利になる場合があります。幸運にもプロジェクトの最初の段階にいる場合、Service Locatorに比べていくつかの利点があるため、DIアプローチの方が適している可能性があります。

そして、ここに私の新しいプロジェクトにService Locatorを使用しないと確信した主な違いがあります:

  • 最も明白で重要:Service Locatorはクラスの依存関係を隠します
  • IoCコンテナーを利用している場合、起動時にすべてのコンストラクターをスキャンしてすべての依存関係を検証し、欠落しているマッピング(または誤った構成)に関するフィードバックを即座に提供します。IoCコンテナーをサービスロケーターとして使用している場合、これは不可能です

詳細については、下記の優れた回答をご覧ください。


「そのクラスをインスタンス化していたすべてのテストを更新する必要はないでしょうか?」テストでビルダーを使用している場合は、必ずしもそうではありません。この場合、ビルダーを更新するだけで済みます。
Peter Karlsson、2014

あなたの言う通り、それは場合によります。たとえば、大規模なAndroidアプリでは、これまでのところ、スペックの低いモバイルデバイスのパフォーマンスへの懸念から、DIを使用することに非常に消極的でした。このような場合は、テスト可能なコードを書く代わりの方法を見つける必要があります。その場合、Service Locatorで十分に代替できます。(注:新しいDagger 2.0 DIフレームワークが十分に成熟すると、Androidでは状況が変わる可能性があります。)
G.ロンバード

1
この質問が投稿されているため、Mark SeemannのService Locatorは、パターン化を解除することによってサービスロケーターがOOPにどのように違反するかを説明するアンチパターンの投稿であり、これが彼の最善の議論です(そしてすべての症状の根本的な理由です)。彼が以前のすべての議論で使用したこと)。アップデート2015-10-26: Service Locatorの根本的な問題は、カプセル化違反することです。
NightOwl888 2016

回答:


125

適合しないいくつかの状況があるという理由だけでパターンをアンチパターンとして定義する場合、はい、それはアンチパターンです。しかし、その推論では、すべてのパターンも反パターンになります。

代わりに、パターンの有効な使用があるかどうかを確認する必要があり、Service Locatorにはいくつかの使用例があります。しかし、あなたが与えた例を見ることから始めましょう。

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コンテナーに新しいマッピングを追加し忘れた可能性があり、DIアプローチでも同じランタイム問題が発生します。

それは本当だ。しかし、コンストラクター注入を使用すると、欠落している依存関係を把握するためにクラス全体をスキャンする必要はありません。

また、一部の優れたコンテナは、起動時にすべての依存関係を検証します(すべてのコンストラクタをスキャンすることにより)。したがって、これらのコンテナを使用すると、実行時エラーが直接発​​生し、後の一時的な時点では発生しません。

また、著者はユニットテストの難しさについて言及しました。しかし、DIアプローチに問題はありませんか?

いいえ。静的サービスロケータへの依存関係はありません。静的依存関係で動作する並列テストを取得しようとしましたか?面白くない。


2
Jgauffin、あなたの答えをありがとう。起動時の自動チェックについて重要なことを1つ指摘しました。私はそれについて考えていませんでした、そして今、私はDIのもう一つの利点を見ます。また、「var myType = new MyType()」の例も示しました。ただし、実際のアプリで依存関係をインスタンス化することはないため、有効なものとして数えることはできません(IoCコンテナーは常にそれを実行します)。つまり、MVCアプリにはコントローラーがあり、IMyServiceとMyServiceImplがIMyRepositoryに依存しています。MyRepositoryとMyServiceをインスタンス化することは決してありません。Ctorパラメータ(ServiceLocatorなど)からインスタンスを取得して使用します。ね?
davidoff 2014

33
Service Locatorがアンチパターンではないことに対する唯一の議論は、「コントロールコンテナーの反転はサービスの場所なしでは機能しない」です。ただし、Mark Seemannによってここで明確に説明されているように、Service Locatorは意図ではなくメカニズムに関するものであるため、この引数は無効です。
スティーブン

4
@jgauffin Web APIは、コントローラーへのDIのサービスロケーションを使用しません。それは全くDIをしません。機能:独自のControllerActivatorを作成して構成サービスに渡すオプションを提供します。そこから、純粋なDIでもコンテナでも、コンポジションルートを作成できます。また、パターンのコンポーネントとしてのサービスロケーションの使用と「サービスロケーターパターン」の定義を組み合わせています。その定義により、コンポジションルートDIは「サービスロケーターパターン」と見なすことができます。したがって、その定義の要点はまったく意味がありません。
Suamere 2014年

1
これはおおむね良い答えであることを指摘したいのですが、あなたがした誤解を招く1つの点についてコメントしました。
Suamere 2014年

2
@jgauffin DIとSLは、どちらもIoCのバージョンレンディションです。SLはそれを行うには間違った方法です。IoCコンテナー SLの場合もあれば、DIを使用する場合もあります。配線方法によって異なります。しかし、SLは悪い、悪い悪い。これは、すべてを密に結合しているという事実を隠す方法です。
MirroredFate 2016

37

また、レガシーコードをリファクタリングしている場合、Service Locatorパターンはアンチパターンであるだけでなく、実際に必要であることも指摘しておきます。誰も何百万行ものコードに魔法の杖を振るつもりはなく、突然、そのすべてのコードがDI対応になる予定です。そのため、既存のコードベースにDIを導入したい場合は、物事をゆっくりとDIサービスに変更することがよくあります。これらのサービスを参照するコードは、多くの場合DIサービスではありません。したがって、THOSEサービスは、DIを使用するように変換されたサービスのインスタンスを取得するために、Service Locatorを使用する必要があります。

したがって、大規模なレガシーアプリケーションをリファクタリングしてDIの概念を使い始めるとき、Service Locatorはアンチパターンではなく、それがコードベースにDIの概念を徐々に適用する唯一の方法であると言います。


12
レガシーコードを扱う場合、たとえそれが中間の(そして不完全な)ステップを取ることを意味する場合でも、その混乱からあなたを取り除くためにすべてが正当化されます。Service Locatorはそのような中間ステップです。文書化され、再現可能であり、効果的であることが証明されている優れた代替ソリューションが存在することを覚えている限り、一度にその一歩を抜け出すことができます。この代替ソリューションは依存性注入であり、これがまさにサービスロケータが依然としてアンチパターンである理由です。適切に設計されたソフトウェアはこれを使用しません。
Steven

RE:「レガシーコードを扱っているとき、その混乱から抜け出すためにすべてが正当化されます」これまでに存在したレガシーコードはほんの少ししか存在しないのではないかと思うことが時々あります。どういうわけかそうすることができませんでした。
Drew Delano

8

テストの観点からは、Service Locatorは不適切です。Misko HeveryのGoogle Tech Talkのわかりやすい説明とコード例http://youtu.be/RlfLCWKxHJ0を 8:45からご覧ください。私は彼の例えが好きでした。25ドルが必要な場合は、お金を受け取る場所から財布を渡すのではなく、直接お金を求めます。彼はまた、Service Locatorを、必要な針があり、それを取得する方法を知っている干し草と比較します。Service Locatorを使用するクラスは、そのため再利用が困難です。


10
これはリサイクルされた意見であり、コメントとしてはより良かったでしょう。また、あなたの(彼の)類推は、いくつかのパターンが他の問題よりもいくつかの問題に適していることを証明するのに役立つと思います。
8bitjunkie 2014年

6

メンテナンスの問題(私を困惑させる)

この点でサービスロケータの使用が不適切である理由は2つあります。

  1. この例では、サービスロケータへの静的参照をクラスにハードコーディングしています。これにより、クラスがサービスロケータに直接密接に結合されます。つまり、サービスロケータなしではクラスは機能しません。さらに、ユニットテスト(およびクラスを使用する他のユーザー)も暗黙的にサービスロケーターに依存しています。ここで気付かれないように思われることの1つは、コンストラクターインジェクションを使用する場合、単体テスト時にDIコンテナーが必要ないため、単体テスト(および開発者がそれらを理解する能力)を大幅に簡略化できることです。これは、コンストラクターインジェクションを使用することで得られる実現された単体テストの利点です。
  2. コンストラクターIntellisenseが重要である理由に関して、ここの人々は要点を完全に逃したようです。クラスは一度だけ作成されますが、いくつかのアプリケーション(つまり、いくつかのDI構成)で使用される場合があります。(できれば最新の)ドキュメントを見るのではなく、コンストラクターの定義を見てクラスの依存関係を理解できる場合、または失敗すると元のソースコードに戻る場合(そうでない場合がある)クラスの依存関係を判断するのに便利です。サービスロケータを備えたクラスは、一般に記述簡単ですが、プロジェクトの継続的なメンテナンスにこの便宜のコストを支払うだけではありません。

単純明快:サービスロケーターが含まれているクラスは、コンストラクターを介して依存関係を受け入れるクラスよりも再利用困難です。

あなたがからサービスを使用する必要がある場合を検討しLibraryA、その作者が使用することを決めたServiceLocatorAとのサービスLibraryBその作者使うことを決めたがServiceLocatorB。プロジェクトで2つの異なるサービスロケーターを使用する以外に選択肢はありません。スピードダイヤルに関する適切なドキュメント、ソースコード、または作成者がいない場合、構成する必要のある依存関係の数は推測ゲームです。これらのオプションに失敗すると、逆コンパイラだけを使用する必要があるかもしれません依存関係を理解するために。2つの完全に異なるサービスロケーターAPIを構成する必要がある場合があり、設計によっては、既存のDIコンテナーを単純にラップできない場合があります。2つのライブラリ間の依存関係の1つのインスタンスを共有することがまったくできない場合があります。サービスロケーターが必要なサービスと同じライブラリに実際に存在しない場合、プロジェクトの複雑さはさらに悪化する可能性があります。追加のライブラリ参照をプロジェクトに暗黙的にドラッグしています。

次に、コンストラクターインジェクションで作成された同じ2つのサービスについて考えます。への参照を追加しますLibraryA。への参照を追加しますLibraryB。DI構成に依存関係を提供します(Intellisenseを介して必要なものを分析します)。できました。

Mark SeemannにはStackOverflowの回答があり、この利点をグラフィック形式明確に示しています。これは、別のライブラリのサービスロケータを使用する場合だけでなく、サービスで外部デフォルトを使用する場合にも適用されます。


1

私の知識はこれを判断するのに十分ではありませんが、一般的に、何かが特定の状況で使用されている場合、それが必ずしもアンチパターンである可能性があるわけではありません。特に、サードパーティのライブラリを扱っている場合、すべての側面を完全に制御できるわけではなく、最終的には非常に最適なソリューションを使用しない可能性があります。

これは、C#を介したアダプティブコードの段落です。

「残念ながら、サービスロケータは避けられないアンチパターンになる場合があります。一部のアプリケーションタイプ(特にWindows Workflow Foundation)では、インフラストラクチャはコンストラクタインジェクションに適していません。これらの場合、唯一の代替策はサービスロケータを使用することです。これは依存関係をまったく注入しないよりも優れています。(アンチ)パターンに対する私のすべてのビトリオールにとって、依存関係を手動で作成するよりもはるかに優れています。結局のところ、デコレータ、アダプタ、および同様の利点。」

-Hall、Gary McLean。C#によるアダプティブコード:デザインパターンとSOLID原則を使用したアジャイルコーディング(開発者リファレンス)(p。309)。ピアソン教育。


0

著者は、「コンパイラーはあなたを助けない」と考えています-そしてそれは本当です。クラスを設計するときは、そのインターフェースを慎重に選択する必要があります。特に、それを理にかなったものとして...

クライアントに、明示的なインターフェースを介してサービスへの参照(依存関係への)を受け入れさせることにより、

  • 暗黙的にチェックを取得するため、コンパイラは「助け」ます。
  • また、クライアントが「ロケーター」または同様のメカニズムについて何かを知っている必要をなくしているので、クライアントは実際により独立しています。

DIには問題/欠点があるが、言及された利点はそれらをはるかに上回っています... IMO。そうです、DIではインターフェース(コンストラクター)に依存関係が導入されていますが、これが必要な非常に重要な依存関係であり、可視性とチェック可能性を高めたいと考えています。


ズリン、あなたの考えをありがとう。私が理解しているように、「適切な」DIアプローチでは、単体テスト以外の場所で依存関係をインスタンス化しないでください。したがって、コンパイラーは私のテストでのみ私を助けます。しかし、最初の質問で述べたように、壊れたテストに関するこの「ヘルプ」では何も得られません。そうですか?
davidoff 2014

「静的情報」/「コンパイル時チェック」の引数はわずらわしいものです。@davidoffが指摘したように、DIも同様に実行時エラーの影響を受けやすくなっています。また、最新のIDEはメンバーのコメント/概要情報のツールチップビューを提供し、そうでない場合でも、誰かがAPIを「知る」までドキュメントを参照することになります。ドキュメンテーションは、必須のコンストラクタパラメータであるか、依存関係の構成方法に関する注釈であるかにかかわらず、ドキュメンテーションです。
tuespetre 2014年

実装/コーディングと品質保証について考える-コードの可読性は、特にインターフェースの定義にとって重要です。自動コンパイル時間チェックなしで実行でき、インターフェースを適切にコメント/ドキュメント化すると、コンテンツを簡単に表示できない/予測できない種類のグローバル変数への非表示依存のデメリットを少なくとも部分的に解決できると思います。この欠点を上回るこのパターンを使用するには、十分な理由が必要だと思います。
Zrin

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.