Service Locatorはカプセル化を解除します

Service Locatorは、静的に型付けされた言語のカプセル化を破ります。これは、このパターンが前提条件をあいまいに表現するためです。



馬は長い間死んでいましたが、まだ乗りたい人もいるので、この馬を再び蹴ります。 何年もの間、 Service Locatorがアンチパターンである理由を説明しようと試みてきました(たとえば、 SOLID違反する )が、最近、私の議論のほとんどが症状に焦点を当てており、根本的な問題がないことに気付きました。



症状に対処するための例として、 元の記事で 、Service Locatorの使用によりIntelliSenseを使用する可能性がどのように悪化しているのかを説明しました。 2010年には、根本的な問題がカプセル化違反であるとは思いもしませんでした。



私の元の例を考えてみましょう:

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); } } }
      
      





この例はC#で記述されていますが、Javaおよび他の同等の静的型付け言語でも同様です。



前提条件と事後条件

カプセル化の主な利点の1つは抽象化です。つまり、ソース内のすべてのコードのすべての実装の詳細を理解する負担から解放されます。 適切に設計されたカプセル化により、実装の詳細を知らなくてもクラスを使用できます。 これは、インタラクションコントラクトを確立することで実現されます。



オブジェクト指向ソフトウェア構築 」という本で説明されているように、契約は相互作用のための事前条件と事後条件のセットで構成されています。 クライアントが前提条件を満たしている場合、オブジェクトは事後条件を満たします。



C#やJavaなどの静的に型付けされた言語では、前述のとおり、多くの前提条件を型システム自体で表現できます。



OrderProcessorクラスのパブリックAPIを見ると、その前提条件は何だと思いますか?

 public class OrderProcessor : IOrderProcessor { public void Process(Order order) }
      
      





ご覧のとおり、多くの前提条件はありません。 APIから確認できる唯一の前提条件は、Processメソッドを呼び出す前に、Order型のオブジェクトが必要であることです。



はい、この前提条件のみを考慮してOrderProcessorを使用しようとすると、実行に失敗します。

 var op = new OrderProcessor(); op.Process(order); // throws
      
      





実際の前提条件は次のとおりです。



3つの前提条件のうち2つは、コンパイル時に表示されません。

ご覧のとおり、このパターンはオブジェクトを正しく使用するための前提条件を隠すため、Service Locatorはカプセル化を解除します。



引数を渡す



いくつかの人々は冗談めかして依存性注入」を「引数を渡す」のではなく、宣伝されている用語として定義しましたが、おそらくこれには少し真実があります。



前提条件を明確にする最も簡単な方法は、型システムを使用して要件を表現することです。 最終的に、Order型のオブジェクトが必要であることにすでに気付きました。 OrderはProcessメソッドの引数型であるため、これは明らかでした。



IOrderValidatorとIOrderShipperの必要性を、同じ手法を使用したOrder型のオブジェクトの必要性と同様に明確にすることはできますか? たぶん、次のコードは解決策ですか?

 public void Process( Order order, IOrderValidator validator, IOrderShipper shipper)
      
      





状況によっては、これですべてのことが必要になる場合があります。現在、3つの前提条件はすべて明白です。



残念ながら、多くの場合、そのような解決策は不可能です。 この場合、OrderProcessorはIOrderProcessorインターフェイスを実装します。

 public interface IOrderProcessor { void Process(Order order); }
      
      





Processメソッドの署名は既に定義されているため、引数を追加することはできません。 型システムから前提条件を表示することもできますが、クライアントは引数を介して必要なオブジェクトを渡す必要があり、クラスの他のメンバーを介して渡すだけです。

コンストラクターが最も安全な方法です。

 public class OrderProcessor : IOrderProcessor { private readonly IOrderValidator validator; private readonly IOrderShipper shipper; public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) { if (validator == null) throw new ArgumentNullException("validator"); if (shipper == null) throw new ArgumentNullException("shipper"); this.validator = validator; this.shipper = shipper; } public void Process(Order order) { if (this.validator.Validate(order)) this.shipper.Ship(order); } }
      
      





この設計により、パブリックAPIは次のようになり始めました。

 public class OrderProcessor : IOrderProcessor { public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) public void Process(Order order) }
      
      





これで、Processメソッドを呼び出すには3つのオブジェクトすべてが必要であることが明らかになりました。 OrderProcessorクラスの最新バージョンは、型システムを通じてその前提条件を促進します。 引数をコンストラクターとメソッドに渡すまでクライアントコードをコンパイルすることさえできません(ここではnullを渡すことができますが、それは別の話です)。



おわりに



Service Locatorは、カプセル化を破るため、静的に型指定されたオブジェクト指向言語のアンチパターンです。 その理由は、このアンチパターンがオブジェクトを正しく使用するための前提条件を隠すためです。



カプセル化を手頃な価格で紹介する必要がある場合は、Pluralsight.comのカプセル化とソリッドコースをご覧ください。 Dependency Injectionの詳細については、.NETの私の本( 受賞歴のあるDependency Injectionを参照してください。



All Articles