私は現在、IoC コンテナーを使用する利点を学び、DI に慣れようとしています。私は StructureMap を使用し始めました。これは、かなり単純でありながら強力に思えるためです。
これらの概念に対する私の理解が正しいことを確認したいと思います。アプリケーションで次の基本的なクラスを想定してみましょう (簡潔にするために詳細は省略されています)。
public class OrderService : IOrderService
{
private IOrderRepository _repository;
private ITaxCalculator _taxCalculator;
private IShippingCalculator _shippingCalculator;
public OrderService(IOrderRepository repository,
ITaxCalculator taxCalculator,
IShippingCalculator shippingCalculator)
{
this._repository = repository;
this._shippingCalculator = shippingCalculator;
this._taxCalculator = taxCalculator;
}
public IOrder Find(int id) { return this._repository.Find(id); }
}
public class OrderRepository : IOrderRepository
{
public IOrder Find(int id) { // ... }
}
public class StandardShippingCalculator : IShippingCalculator
{
// ...
}
public class StandardTaxCalculator : ITaxCalculator
{
private ITaxSpecification _specification;
public StandardTaxCalculator(ITaxSpecification specification)
{
this._specification = specification;
}
}
まず第一に、Dependency Inversion Principle は、OrderService は「高レベル」モジュールであるため、下位レベルの実装の詳細に依存するべきではなく、それらのクラスへの参照を持ち、それらに要求できるようにする必要があると述べています。消費するコードは、これらの事前構成されたモジュールを作成してそれに渡す責任を負う必要があります。そうですか?したがって、DI はこれらのクラスを疎結合に保ち、その依存関係のメソッドが呼び出される正確な方法を知る必要がないようにします。呼び出されて、必要なことは何でも実行します。OrderService は、リポジトリが XML を照会しているか、NHibernate や EF、さらには生の DataSet を使用しているかを気にしません。リポジトリを呼び出して、ID が 42 の Order を見つけるように指示するだけで、リポジトリは何をすべきかを認識します。
また、IoC コンテナー (この場合は StructureMap) が、これらすべての依存関係を手動で作成して渡すことを強制しないことでメリットがあることも理解しています。たとえば、簡単なアプリの Main メソッドを使用して、上記のコードには次のものがある可能性があります。
static void Main(string[] args)
{
IOrderService service = new OrderService(
new OrderRepository(), new StandardShippingService(),
new StandardTaxService(new StandardTaxSpecification()));
IOrder order = service.Find(42);
// Do something with order...
}
これは、それを設定するためのすべてのニュースで非常に醜いです。変数を作成したとしても、それはまだ醜いです。IoC コンテナーを使用すると、これらすべてを回避できます。StructureMap の場合は次のようになります。
static void Main(string[] args)
{
ObjectFactory.Initialize(x =>
{
x.For<IOrderRepository>().Use<OrderRepository>();
x.For<IOrderService>().Use<OrderService>();
x.For<IOrder>().Use<Order>();
x.For<IShippingCalculator>()
.Use<StandardShippingCalculator>();
x.For<ITaxCalculator>().Use<StandardTaxCalculator>();
x.For<ITaxSpecification>()
.Use<StandardTaxSpecification>();
});
IOrderService service =
ObjectFactory.GetInstance<IOrderService>();
IOrder order = service.Find(42);
// do stuff with order...
}
これははるかにクリーンで、保守が容易で、単体テストを書いている場合は、たとえばモックの具象クラスをサブアウトできます。要するに、利点は、特定のクラスが何に依存しているか (つまり、呼び出し元のコード) を気にする必要さえないところまで、すべてをさらに分離できることです。コンテナーを使用してクラスを作成し、実行させることができます。それは問題であり、消費するコードが必要なものだけを知っていることを確認します。たとえば、実際のアプリでは、コントローラーがサービスを呼び出している場合、リポジトリ、電卓、または仕様について知る必要はありません。知る必要があるのは、使用することだけですOrderService で Order を処理します。
この理解は正しいですか?その点については、まだわからないことがいくつかあります。
IoC コンテナーを使用することにした場合、それはアプリケーションのあらゆる場所で使用されることを意図していますか?それは、解決する逆依存関係が多数ある場合のみですか?それともコンシューマーでのみ使用することを意図していますか? たとえば、具体的な実装を使用して Order を新規作成している場合、OrderRepository で。このクラスは、Order を取得するために StructureMap も使用しますか? これはややばかげた質問かもしれませんが、私が見たすべての DI/IoC の例は、消費するクライアント (Web ページなど) での使用にのみ焦点を当てており、他の場所での使用には決して触れていません。それは全か無かのアプローチのようです。IoC コンテナーを使用する場合は、どこでも使用されます。
new SomeObject();
この場合、基本的に呼び出しを次のように置き換えています。ObjectFactory.GetInstance<ISomeObject>();
DI/IoC を使用する必要があるかどうか、またはそれをモックするようなものを使用する必要があるかどうかにかかわらず、すべてのクラス (もちろん可能であれば) をインターフェイスから派生させることは良いことでしょうか? 組み込みクラスではないすべてのクラスの背後にインターフェースがある多くのコード例を見てきましたが、これを行うことの利点と将来の保証の可能性を見ることができます.TDDまたはBDDに従うことはこれらの方法論を使用すると、通常、クラスのインターフェイスが必要かどうかがわかりますが、TDD であろうとなかろうと、オブジェクトの型を具体的なクラスとして定義するべきではないと感じている多くの人々を見て話しました。それは常にすべきです基になる型としてインターフェイスまたは抽象クラスになります。これは、YAGNI 違反は言うまでもなく、"Needless Complexity" コードの臭いのケースのようです。