グーグルで調べた後、コンストラクターインジェクションとプロパティ/フィールドインジェクションのどちらが優れているかについていくつかの議論がありましたが、より有益であると思う別の方法があります。
ほとんどのプログラミング環境では、「呼び出しコンテキスト」と呼ばれるものがあります。これは、呼び出し元から呼び出し先に暗黙的に渡される情報です。
このコンテキストは単にスレッドローカルストレージである場合もあれば、クロススレッド非同期プログラミングの場合はそれよりも複雑な場合もあります。
いずれの場合でも、そのようなコンテキストには、カルチャ、ユーザー、またはWebアプリケーションの場合は現在の要求などの情報が格納されます。
これらのタイプの情報が明示的に持ち越されることはほとんどありませんが、前述のコンテキストにアクセスするグローバルメソッドを介してアクセスされます。
依存関係注入の使用例では、これは静的/グローバル形式のサービスロケータを意味します(私の例はC#にあります)。
public static Sl
{
public static Dependency Get<Dependency>();
public static IDisposable Push<Dependency>(Dependency dependency);
}
使用法は次のとおりです。
public void SomeFunction()
{
Sl.Get<Dependency>().UseItSomehow();
}
SomeFunction
を呼び出すには、次のように呼び出し先の依存関係を設定または変更できます。
using (Sl.Push<SomeDependency>(someImplementation)
{
SomeFunction();
}
このパターンは、他のタイプの依存性注入よりも優れていると思いますより大きな呼び出しサブツリーの大部分に依存性が均一に必要な場合。
たとえば、リポジトリ、ロガー、または構成インターフェースが単一の関数にのみ必要になることはめったになく、それに続くすべてのネストされた呼び出しにも必要です。
特にコンストラクターインジェクションを使用すると、完全に回避できる混乱を招きます。上記のSomeFunction
は、依存関係を自動的に取得する他の関数を呼び出すことができます。また、実際に必要な場合に依存関係を変更することもできます。
私がどこかで読んだと思う懸念のいくつかに取り組みたいのですが、私の質問は、誰かが他の人について考えることができるかどうかです。
Sl
への依存関係があります
これは原理主義者の異議として私を襲います。ほとんどのソフトウェアは整数、文字列、リストにも依存していますが、そのようなものが適切に抽象化され、純粋なDependency Injection Injectionに関して適切に渡されることはありません。明らかにsome直接依存する必要がある低レベルのもの-うまくいけば、それらはうまく設計されており、理想的には、それぞれの言語のランタイムライブラリに存在します。そうでない場合でも、それらはより小さな悪である可能性があります。
必要な依存関係は明示的である必要があります
DIの本当に明示的な形式は、コンストラクターの注入、またはメソッド呼び出しで直接依存関係を渡すことです。残念ながら、これは最も冗長で柔軟性のない方法でもあります。後で追加される依存関係は、コンストラクターまたはメソッドのシグネチャを変更し、オブジェクトをそのまま渡すだけで機能するいくつかのレイヤーの関数呼び出しのリファクタリングを必要とします。静的言語では、これは面倒です。動的なものでは、それはバグの原因ですらあります。
さらに、依存関係が必要な場合、とにかく使用するたびにそれぞれの機能が失敗し、依存関係の欠落のバグが誤って本番環境に移行しない可能性が高くなります。
理想的には、はい、必要な依存関係は明示的である必要があります。しかし、常にトレードオフがあり、コンストラクターインジェクションフォークの優先順位がまっすぐではないと考えるのは、私だけですか?
魔法に頼ってはいけない
コールコンテキストとスレッドスタティックは魔法ではありません。それらは単に高度で、おそらく大学の一般的なプログラミングコースでカバーされているものよりも技術的です。
また、実装は静的サービスロケータの背後に隠されています。ユーザーは、それを使用するためにどのように機能するかを知る必要はありません。
これに加えて、DIファンの間には奇妙な偽善もあります。たとえば、ASP.NET Coreを例にとります。これらの人たちは、文字どおりDIパラダイムをユーザーに強制しています。その結果、既存の依存関係の実現を特に困難にする設計になります。まるで自分をからかっているようです。
認証やカスタムヘッダーの設定など、送信Webリクエストの特定の構成を、それを使用しているコードにバインドする方法を検討してください。最初に構成コード(HttpClient
およびHttpClientFactory
)を実行します。グローバルサービスコンテナーに配置します。次に、クライアントが必要な場所はどこでも、コンテナから取り出されます。つまり、構成コードが欠落している場合でも、すべてがコンパイルされ、実行時まで気付くことはありません。
また、使用ポイントごとに構成が異なる場合はどうなりますか? ASP.NET Coreのソリューション:特別に構成されたHttpClientFactory
sをマジックストリングに関連付け、それらのマジックストリングを使用して、必要な場所で正しいストリングを見つけ出します。
私をからかってるの?何が起きてる?
この狂気がプログラマーの心を一掃する前は、依存関係はmore明示的でした。
私は今、面倒な指のエクササイズ(ctor注入)を行い、正しい合成可能性を確認するためのコンパイル時のチェックなしで、これらの型をすべて1つのグローバルコンテナーに投げるように求められます。
だから私の質問:静的/グローバルサービスロケーターを使用したDIが悪くなる正当な実際の理由はありますか、それともこの不幸な狂乱に対処するための賢明な方法であると私は正しいのですか?
他に異論はありますか?静的/グローバルサービスロケータを使用したDIが悪い理由として、正当で現実的な理由はありますか?
ええ、はい。
Statics/globalsは恐ろしいものです。アプリケーションのランタイムは同種であると想定しています。すべてのインスタンスには、すべて同じ種類のインスタンスがすべて必要です。それは素朴です。これらは、anyのすべての状態が本質的に共有される状態であるため、同時実行性を妨げます。同時に実行されているさまざまなインスタンスや、コールスタックの下にあるさまざまなインスタンスを効果的に模擬することができないため、テストが妨げられます。そして、グローバル状態に関する通常のデバッグ問題があります。
問題を非表示にしても問題が解決するわけではありません。すべてに必要なものがあれば、広範囲に結合しています。多数の依存関係を必要とするオブジェクトがある場合は、広範囲にわたる結合があります。巨大なオブジェクト階層があり、移動する必要がある場所に物事を渡すことが困難な場合は、おそらく設計が貧弱です。 DIはこれらのものを修正しません!あなたがしていることは、依存関係を渡す魔法の後ろにそれを隠すことです。依存関係を削除しているわけではなく、コンパイル時のエラーではなく、実行時のエラーがわかりにくくなるだけです。
私はあなたが依存性注入の利点を見逃していると思います-クラスが依存するものは何でも想定されているのではなくそれに与えられているということです。あなたが提案しているのはservice locatorアンチパターンです。私がそれ(アイデンティティ)を使用したくなるかもしれないいくつかのケースがありますが、私は1つの理由でクリーンな契約を好むでしょう-私が誰かが私のコードをどのように使用するのかわからないのです。私はこれを職場で見ました。誰かがウェブコンセプト(HttpContext
など)への周囲参照を、コードをインジェクトするのではなく、コールチェーンの奥深くでコードを設計しています。 1年か2年後、静的な依存関係のため、コードはコンソールアプリケーションで再利用できません。単体テストを作成しようとする場合も同様です。
S1
[.____への依存関係があります。]サーバーロケータのアンチパターンとそれを回避する理由についての他の議論へのリンクをいくつか次に示します。
http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/
https://www.youtube.com/watch?v=RlfLCWKxHJ0&feature=youtu.be
https://stackoverflow.com/questions/22795459/is-servicelocator-anti-pattern
私自身も全く同じ考えを持っていたので、これは素晴らしい考えだと思います。私はそれが少なくとも非常に似たようなことをしたので、それが実行可能であることを知っています。私は他のどのような異議が提起されているかを確認することに非常に興味があります。これまでのところ、実質的なものではないと思います。最も興味深いのは http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/ リンク内です。要するに、それはのサードパーティの消費者が
SomeFunction()
強制されていないため、サービスロケータの設定に失敗する可能性があります。
SomeFunction(dependency)
コンパイラーは依存関係を強制的に提供します。ただし、SomeFunctionを使用してサードパーティのコードで依存関係を提供する必要があることは、ファーストパーティのコードと同じくらい問題になる傾向があると思います。
ロガーの場合、一般的に使用される関数にパラメーターを導入するという古典的な連鎖反応の問題があります。関数の呼び出し元はそのパラメーターを見つける必要があり、通常はパラメーター自体が必要になるなど、呼び出しツリーに大きな波及効果を引き起こします。ロガーにDIを使用する推奨コンテキストでは、一部の関数にロギングを追加する必要があるコード変更により、関数が使用される場所からチェーンのすべてのクラスのコンストラクターにロガーサービスインターフェイスを追加する必要があります。
これは、このユースケースではDIのメンテナンスコストが大きく、管理が非常に困難な場合があります。
このコストは、ロガーが必要なコードベースに分離されていません。 SomeFunctionがサードパーティによって使用されている例では、ロギングサービスをサービスロケータに1回提供する方が、それを必要とするすべてのサードパーティコードのコンストラクタにログサービスを書き込むよりも効率的です。
これは、グローバルコンテキストが存在することには意味があるのでしょうか。コードのすべてのレベルで広く使用されているデータオブジェクトを、それらを渡すための多数のパラメーターを作成することなく、はるかに簡単にアクセスできるようにするため、その答えはyesである必要があります。