public class OrderProcessor : IOrderProcessor { public void Process(Order order) { var validator = Locator.Resolve<IOrderValidator>(); if (validator.Validate(order)) { var shipper = Locator.Resolve<IOrderShipper>(); shipper.Ship(order); } } }
ご覧のとおり、 Processメソッドで静かに解決される2つの隠れた依存関係により、 OrderProcessor型のカプセル化が壊れています 。 これらの依存関係は呼び出し元のコードから隠されているため、クライアントがIoCコンテナーを適切に構成しておらず、必要な依存関係を決定していない場合、ランタイムで例外が発生する可能性があります。 この問題の解決策として、Markは依存関係の解決をオブジェクトのコンストラクターに転送することを提案しています。
public class OrderProcessor : IOrderProcessor { public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) public void Process(Order order) }
これにより、呼び出し元のコードは、 OrderProcessorオブジェクトが実際に必要とするものを認識します 。
しかし、私の意見では、依存関係の非表示を適用できるシナリオはまだあります。 ほとんどすべてのViewModelがIEventAggregator、IProgress、IPromptCreatorの依存関係を必要とするWPFアプリケーションを検討してください。 最後の2つのインターフェイスの意味を明らかにするために、 IProgress実装は長時間実行されるコードを受け入れて、進行状況バーを含むウィンドウを表示できる必要があることを追加します。IPromptCreatorでは、確認、同意、または拒否(モーダルダイアログ)が必要なウィンドウを開くことができます 。 ここで、モデルを作成するためにさらに2つ(または3つ)の依存関係を必要とするViewModelがあることを想像してください。 ここに、 ViewModelが非常に多くの依存関係を持つように見える方法を示します。
public class PaymentViewModel: ICanPay { public PaymentViewModel(IPaymentSystem paymentSystem, IRulesValidator rulesValidator, IEventAggregator aggregator, IProgress progress, IPromptCreator promptCreator) public void PayFor(Order order) }
なんてこった! コンストラクター宣言のノイズが多すぎます。 ビジネスロジックの観点から見ると、実際に役立つ情報を保持している依存関係は2つだけです。
たとえば、MEFを使用して依存関係を注入する場合、次のことができます。
[Export] public class PaymentViewModel : ICanPay { [Import] protected IEventAggregator aggregator; [Import] protected IProgress progress; [Import] protected IPromptCreator promptCreator; public PaymentViewModel(IPaymentSystem paymentSystem, IRulesValidator rulesValidator) { } public void PayFor(Order order) { //use aggreagtor, progress, promptCreator } }
依存関係をコンストラクターからフィールド宣言に転送し、 Import属性でマークしました。 IoCコンテナーを直接呼び出すことはありませんが(MEFは純粋なIoCコンテナーではありませんが)、Markの例と同じ方法で依存関係を非表示にします。 基本的に何も変わっていません。 このコードはそれほど悪くないと思うのはなぜですか? いくつかの主な理由により:
- ViewModelはビジネスエンティティではなく、 モデルとビューを接着するためのコードの一部にすぎません。 上記の依存関係を本当に気にする人はいません。
- ViewModelはパブリックAPIではなく、再利用されません(ほとんどの場合)。
- 前の2つの点を考えると、チームメンバーは、すべてのViewModelにこれらの実用的な依存関係があることに単純に同意できます。
- これらのユーティリティの依存関係は保護されていると定義されています。これにより、テストでPaymentViewModelを継承するViewModelクラスを作成し、依存関係をモックに置き換えることができます。 したがって、 PaymentViewModelをテストでカバーする機会を失うことはありません。 マークの例( Service Locatorが使用されている場合 )では、これらの依存関係をロックまたは停止するために、ユニットテストプロジェクトでIoCコンテナーを構成する必要がありました。この方法は、ユニットテストプロセスにとって苦痛になります。
おわりに
私が言ったように、プログラミングでは、あらゆる場面で正しい答えや声明はありません。 一般的に言えば、Markが彼の記事で述べているように、これはカプセル化に違反するため、 Service Locatorの使用を避ける必要があります。 Service Locatorを使用する前に、システムの潜在的な損傷を評価してください。 依存関係の隠された解決がクライアントコード(チームメイトが記述できる)に影響を与えず、システムに潜在的な害がないことが確実な場合は、歌を続けます。