これがまた別の質問のように思われた場合は申し訳ありませんが、トピックに関する記事を見つけるたびに、ほとんどの場合、DIとは何かについて話します。だから、私はDIを取得しますが、誰もが入りそうなIoCコンテナーの必要性を理解しようとしています。 IoCコンテナのポイントは、依存関係の具体的な実装を「自動解決」するだけなのでしょうか。たぶん、私のクラスにはいくつかの依存関係がない傾向があるかもしれません。それがおそらく私が大したことを見ない理由ですが、私はコンテナのユーティリティを正しく理解していることを確認したいと思います。
私は通常、ビジネスロジックを次のようなクラスに分割します。
public class SomeBusinessOperation
{
private readonly IDataRepository _repository;
public SomeBusinessOperation(IDataRespository repository = null)
{
_repository = repository ?? new ConcreteRepository();
}
public SomeType Run(SomeRequestType request)
{
// do work...
var results = _repository.GetThings(request);
return results;
}
}
したがって、依存関係は1つだけで、場合によっては2番目または3番目の依存関係が存在することもありますが、それほど多くはありません。したがって、これを呼び出すものは、それ自体のリポジトリを渡すか、デフォルトのリポジトリを使用することができます。
IoCコンテナーに関する私の現在の理解に関する限り、コンテナーが行うことは、IDataRepositoryを解決することだけです。しかし、それだけの場合は、依存関係が渡されない場合のフォールバックが操作クラスで既に定義されているため、大量の値は表示されません。したがって、他に考えられる唯一の利点は、これは同じフォールバックリポジトリを使用します。レジストリ/ファクトリ/コンテナである1つの場所でそのリポジトリを変更できます。それは素晴らしいことですが、それはそれですか?
IoCコンテナは、1つの依存関係がある場合のものではありません。これは、3つの依存関係があり、それらにいくつかの依存関係があり、依存関係などがある場合です。
また、依存関係の解決を一元化し、依存関係のライフサイクル管理を支援します。
IoCコンテナーを使用する理由はいくつかあります。
参照されていないDLL
IoCコンテナーを使用して、参照されていないdllから具象クラスを解決できます。これは、抽象化、つまりインターフェースに完全に依存できることを意味します。
new
の使用を避けます
IoCコンテナは、クラスを作成するためのnew
キーワードの使用を完全に削除できることを意味します。これには2つの効果があります。 1つ目は、クラスを分離することです。 2つ目(これは関連しています)は、単体テストのためにモックをドロップできることです。これは、特に実行時間の長いプロセスとやり取りしているときに非常に役立ちます。
抽象化に対する書き込み
IoCコンテナを使用して具体的な依存関係を解決することで、必要に応じて必要なすべての具象クラスを実装するのではなく、抽象化に対してコードを記述できます。たとえば、データベースからデータを読み取るためにコードが必要になる場合があります。データベース相互作用クラスを作成する代わりに、そのためのインターフェースとそれに対するコードを作成するだけです。他のコードをテストする前に具象データベース相互作用クラスの開発に頼るのではなく、モックを使用して、開発中のコードの機能をテストできます。
もろいコードは避けてください
IoCコンテナーを使用するもう1つの理由は、IoCコンテナーを使用して依存関係を解決することにより、依存関係を追加または削除するときにクラスコンストラクターへのすべての呼び出しを変更する必要がなくなるためです。 IoCコンテナは依存関係を自動的に解決します。これは、一度クラスを作成する場合は大きな問題ではありませんが、クラスを100か所に作成する場合は大きな問題です。
ライフタイム管理と管理されていないリソースのクリーンアップ
私が言及する最後の理由は、オブジェクトのライフタイムの管理です。 IoCコンテナーは、多くの場合、オブジェクトの存続期間を指定する機能を提供します。コードでオブジェクトを手動で管理するのではなく、IoCコンテナー内のオブジェクトの存続時間を指定することは非常に理にかなっています。手動での寿命管理は非常に困難な場合があります。これは、破棄が必要なオブジェクトを処理するときに役立ちます。オブジェクトの破棄を手動で管理する代わりに、一部のIoCコンテナーは破棄を管理します。これにより、メモリリークを防止し、コードベースを簡略化できます。
提供したサンプルコードの問題は、作成しているクラスが、ConcreteRepositoryクラスへの具体的な依存関係を持っていることです。 IoCコンテナはその依存関係を削除します。
単一責任の原則によれば、すべてのクラスは単一の責任のみを持つ必要があります。クラスの新しいインスタンスの作成は、もう1つの責任であるため、この種のコードを1つ以上のクラスにカプセル化する必要があります。これは、工場、ビルダー、DIコンテナーなどの任意の作成パターンを使用して行うことができます。
制御の反転や依存関係の反転など、他の原則があります。このコンテキストでは、依存関係のインスタンス化に関連しています。彼らは、高レベルのクラスは、使用する低レベルのクラス(依存関係)から分離する必要があると述べています。インターフェースを作成することで、物事を切り離すことができます。したがって、低レベルのクラスは特定のインターフェースを実装する必要があり、高レベルのクラスはこれらのインターフェースを実装するクラスのインスタンスを利用する必要があります。 (注:REST統一インターフェース制約は、システムレベルで同じアプローチを適用します。)
例によるこれらの原則の組み合わせ(低品質のコードの場合は申し訳ありませんが、C#の代わりにアドホック言語を使用しました。
SRPなし、IoCなし
class SomeHighLevelService
{
public doFooBar(){
Crap crap = doFoo();
doBar(crap);
}
public Crap doFoo(){
//...
return crap;
}
public doBar(Crap crap){
//...
}
}
SomeHighLevelService service = new SomeHighLevelService();
service.doFooBar();
SRPに近い、IoCなし
class SomeHighLevelService
{
public SomeHighLevelService(){
Foo foo = new Foo();
Bar bar = new Bar();
}
public doFooBar(){
Crap crap = foo.doFoo();
bar.doBar(crap);
}
}
class Foo {
public Crap doFoo(){
//...
return crap;
}
}
class Bar {
public doBar(Crap crap){
//...
}
}
SomeHighLevelService service = new SomeHighLevelService();
service.doFooBar();
はいSRP、いいえIoC
class HighLevelServiceProvider {
public SomeHighLevelService getSomeHighLevelService(){
SomeHighLevelService service = new SomeHighLevelService();
service.setFoo(this.getFoo());
service.getBar(this.getBar());
return service;
}
private Foo getFoo(){
return new Foo();
}
private Bar getBar(){
return new Bar();
}
}
class SomeHighLevelService
{
public setFoo(Foo foo){
this.foo = foo;
}
public setBar(Bar bar){
this.bar = bar;
}
public doFooBar(){
Crap crap = foo.doFoo();
bar.doBar(crap);
}
}
class Foo {
public Crap doFoo(){
//...
return crap;
}
}
class Bar {
public doBar(Crap crap){
//...
}
}
HighLevelServiceProvider provider = new HighLevelServiceProvider();
SomeHighLevelService service = provider.getSomeHighLevelService();
service.doFooBar();
はいSRP、はいIoC
interface HighLevelServiceProvider {
SomeHighLevelService getSomeHighLevelService();
}
interface SomeHighLevelService {
doFooBar();
}
interface Foo {
Crap doFoo();
}
interface Bar {
doBar(Crap crap);
}
class ConcreteHighLevelServiceContainer implements HighLevelServiceProvider {
public SomeHighLevelService getSomeHighLevelService(){
SomeHighLevelService service = new ConcreteHighLevelService();
service.setFoo(this.getFoo());
service.getBar(this.getBar());
return service;
}
private Foo getFoo(){
return new ConcreteFoo();
}
private Bar getBar(){
return new ConcreteBar();
}
}
class ConcreteHighLevelService implements SomeHighLevelService
{
public setFoo(Foo foo){
this.foo = foo;
}
public setBar(Bar bar){
this.bar = bar;
}
public doFooBar(){
Crap crap = foo.doFoo();
bar.doBar(crap);
}
}
class ConcreteFoo implements Foo {
public Crap doFoo(){
//...
return crap;
}
}
class ConcreteBar implements Bar {
public doBar(Crap crap){
//...
}
}
HighLevelServiceProvider provider = new ConcreteHighLevelServiceContainer();
SomeHighLevelService service = provider.getSomeHighLevelService();
service.doFooBar();
したがって、すべての具体的な実装を、同じインターフェイスofcを実装する別の実装に置き換えることができるコードができあがりました。参加しているクラスは互いに分離されているので、これは良いことです。それらはインターフェースだけを知っています。インスタンス化のコードが再利用可能なもう1つの利点。