設計の「匂い」としての隠された依存関係

Mark Siman、「Service Locatorがカプセル化を破る」というすばらしい記事を書いています。 投稿のタイトルは、 Service Locatorパターン(アンチパターン)専用であることを物語っています 。 プログラマーがコード内でIoCコンテナーをランダムに呼び出してオブジェクトの依存関係を解決するとき、 Service Locator anti \パターンを使用します 。 マークは次の例を考慮します。

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オブジェクトが実際に必要とするものを認識します



しかし、私の意見では、依存関係の非表示を適用できるシナリオはまだあります。 ほとんどすべてのViewModelIEventAggregator、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の例と同じ方法で依存関係を非表示にします。 基本的に何も変わっていません。 このコードはそれほど悪くないと思うのはなぜですか? いくつかの主な理由により:



おわりに



私が言ったように、プログラミングでは、あらゆる場面で正しい答えや声明はありません。 一般的に言えば、Markが彼の記事で述べているように、これはカプセル化に違反するため、 Service Locatorの使用を避ける必要があります。 Service Locatorを使用する前に、システムの潜在的な損傷を評価してください。 依存関係の隠された解決がクライアントコード(チームメイトが記述できる)に影響を与えず、システムに潜在的な害がないことが確実な場合は、歌を続けます。



All Articles