DI / IoC コンテナーの基本的な概念を理解していると思います。それらを使用していくつかのアプリケーションを作成し、多くのスタック オーバーフローの回答と Mark Seeman の本を読んでいます。特に、DI の原則が実際には使用されていない大規模な既存のアーキテクチャに DI コンテナーを統合する場合 (大きな泥のボールを考えてください)、問題を抱えているケースがまだいくつかあります。
理想的なシナリオは、操作ごとに単一のコンポジション ルート/オブジェクト グラフを持つことですが、レガシー システムでは、大幅なリファクタリングなしでは不可能な場合があります (コードの新しい部分と一部のリファクタリングされた古い部分のみが、コンストラクターを介して依存関係を注入でき、システムの残りの部分は、コンテナーをサービス ロケーターとして使用して、新しいパーツと対話します)。これは事実上、操作の奥深くにあるスタック トレースに複数のオブジェクト グラフが含まれ、新しいサブシステム (古いセグメントに出るまで単一のオブジェクト グラフ) と従来のサブシステム (ある時点でのコードの下のコードへのサービス ロケーター呼び出し) の間で呼び出しが行われる可能性があることを意味します。 DIコンテナ)。
(潜在的に間違っている可能性があります。私はこれを考えすぎているか、この種のハイブリッドアーキテクチャが良い考えであると仮定するのは完全に間違っているかもしれません)邪魔にならない仮定で、ここに実際の問題があります:
データベース(または外部の場所)で定義されたさまざまなタイプのスケジュールされたジョブを実行するスレッドプールがあるとします。スケジュールされたジョブの個別のタイプは、共通の基本クラスを継承するクラスとして実装されます。ジョブが開始されると、ログ メッセージを書き込むターゲットと使用する構成に関する情報が与えられます。構成はおそらく、メソッドのパラメーターとして値を必要とするクラスに渡すだけで処理できますが、ジョブの実装が 10 ~ 20 クラスよりも大きくなると、あまり便利ではないようです。
ロギングはより大きな問題です。ジョブが呼び出すサブシステムもおそらくログに書き込む必要があり、通常、例ではコンストラクターで ILog のインスタンスを要求するだけでこれが行われます。しかし、実行時まで詳細/実装がわからない場合、この場合はどのように機能するのでしょうか? 以来:
- コール チェーン内の (非 DI コンテナー制御の) レガシー システム セグメント (-> 複数の個別のオブジェクト グラフが存在する可能性がある) のため、子コンテナーを使用して特定のサブスコープのカスタム ロガーを挿入することはできません。
- 手動のプロパティ インジェクションでは、基本的に完全な呼び出しチェーン (すべてのレガシー サブシステムを含む) を更新する必要があります。
問題をよりよく理解するのに役立つ簡単な例:
Class JobXImplementation : JobBase {
// through constructor injection
ILoggerFactory _loggerFactory;
JobXExtraLogic _jobXExtras;
public void Run(JobConfig configurationFromDatabase)
{
ILog log = _loggerFactory.Create(configurationFromDatabase.targets);
// if there were no legacy parts in the call chain, I would register log as instance to a child container and Resolve next part of the call chain and everyone requesting ILog would get the correct logging targets
// do stuff
_jobXExtras.DoStuff(configurationFromDatabase, log);
}
}
Class JobXExtraLogic {
public void DoStuff(JobConfig configurationFromDatabase, ILog log) {
// call to legacy sub-system
var old = new OldClass(log, configurationFromDatabase.SomeRandomSetting);
old.DoOldStuff();
}
}
Class OldClass {
public void DoOldStuff() {
// moar stuff
var old = new AnotherOldClass();
old.DoMoreOldStuff();
}
}
Class AnotherOldClass {
public void DoMoreOldStuff() {
// call to a new subsystem
var newSystemEntryPoint = DIContainerAsServiceLocator.Resolve<INewSubsystemEntryPoint>();
newSystemEntryPoint.DoNewStuff();
}
}
Class NewSubsystemEntryPoint : INewSubsystemEntryPoint {
public void DoNewStuff() {
// want to log something...
}
}
この時点でイメージがつかめると思います。
DI を使用して古いクラスをインスタンス化することは、多くの場合 (多くの場合複数の) コンストラクターを使用して依存関係の代わりに値を注入し、1 つずつリファクタリングする必要があるため、開始点ではありません。呼び出し元は基本的にオブジェクトの有効期間を暗黙的に制御し、これは実装で想定されています (オブジェクトの内部状態を処理する方法)。
私のオプションは何ですか?このような状況では、他にどのような種類の問題が発生する可能性がありますか? この種の環境でコンストラクター注入のみを使用しようとすることは実現可能ですか?