依存関係の注入を取得しますが、IoCコンテナーの必要性を理解するのを手伝ってもらえますか?


15

これが質問のさらに別の繰り返しのように思える場合は申し訳ありませんが、トピックに関する記事を見つけるたびに、それは主に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つの場所でそのリポジトリを変更できます。それは素晴らしいことですが、それはそれですか?


1
多くの場合、デフォルトのフォールバックバージョンの依存関係を使用するのはあまり意味がありません。
ベンアーロンソン14

どういう意味ですか?「フォールバック」は、単体テストを除いて、ほとんど常に使用される具体的なクラスです。実際には、これはコンテナに登録されているのと同じクラスになります。
シナエステ14

はい、ただし、コンテナの場合:(1)コンテナ内の他のすべてのオブジェクトは同じインスタンスを取得し、ConcreteRepository(2)追加の依存関係を提供できます(たとえばConcreteRepository、データベース接続が一般的です)。
ジュール

@Sinaesthetic常に悪い考えだとは限りませんが、しばしば適切ではありません。たとえば、プロジェクト参照でタマネギアーキテクチャに従うことができなくなります。また、明確なデフォルトの実装がない場合もあります。ジュールが言うように、IOCのコンテナはちょうど依存タイプを選ぶない管理されますが、共有インスタンスとライフサイクル管理のようなことを行う
ベン・アーロンソン

「Function Parameters-The ORIGINAL Dependency Injection!」というTシャツを作成します。
グラハム

回答:


2

IoCコンテナーは、1つの依存関係がある場合についてではありません。それは、3つの依存関係があり、依存関係などがあるいくつかの依存関係がある場合です。

また、依存関係の解決と依存関係のライフサイクル管理を集中化するのにも役立ちます。


10

IoCコンテナーを使用する理由はいくつかあります。

参照されていないdll

IoCコンテナを使用して、参照されていないdllから具象クラスを解決できます。これは、抽象化、つまりインターフェースに完全に依存できることを意味します。

の使用を避ける new

IoCコンテナとはnew、クラスを作成するためのキーワードの使用を完全に削除できることを意味します。これには2つの効果があります。1つ目は、クラスを分離することです。2番目(関連する)は、単体テストのためにモックをドロップできることです。これは、特に長時間実行されているプロセスとやり取りする場合に非常に便利です。

抽象化に対する書き込み

IoCコンテナーを使用して具体的な依存関係を解決すると、必要なすべての具体的なクラスを実装するのではなく、抽象化に対してコードを記述できます。たとえば、データベースからデータを読み取るためにコードが必要になる場合があります。データベースインタラクションクラスを記述する代わりに、単純にそのインターフェイスとそれに対するコードを記述します。他のコードをテストする前に具体的なデータベース相互作用クラスの開発に頼るのではなく、モックを使用して、開発中のコードの機能をテストできます。

脆弱なコードを避ける

IoCコンテナーを使用するもう1つの理由は、依存関係を解決するためにIoCコンテナーに依存することにより、依存関係を追加または削除するときにクラスコンストラクターのすべての呼び出しを変更する必要がないことです。IoCコンテナーは依存関係を自動的に解決します。これは、クラスを1回作成するときの大きな問題ではありませんが、100か所でクラスを作成するときの大きな問題です。

ライフタイム管理と管理されていないリソースのクリーンアップ

最後に言及する理由は、オブジェクトの有効期間の管理です。IoCコンテナは、多くの場合、オブジェクトの有効期間を指定する機能を提供します。コード内でオブジェクトを手動で管理するのではなく、IoCコンテナー内のオブジェクトの有効期間を指定することは非常に理にかなっています。手動のライフタイム管理は非常に難しい場合があります。これは、廃棄が必要なオブジェクトを処理するときに役立ちます。オブジェクトの廃棄を手動で管理する代わりに、一部のIoCコンテナが廃棄を管理します。これにより、メモリリークを防ぎ、コードベースを簡素化できます。

提供したサンプルコードの問題は、作成しているクラスがConcreteRepositoryクラスに具体的に依存していることです。IoCコンテナーはその依存関係を削除します。


22
これらはIoCコンテナーの利点ではなく、依存性注入の利点です。これは、貧乏人のDIで簡単に行うことができます
ベンアーロンソン14

IoCコンテナなしで適切なDIコードを記述することは、実際にはかなり困難です。はい、利点には多少の重複がありますが、これらはすべてIoCコンテナーで最も活用される利点です。
スティーブン14

さて、あなたは私のコメント以降に追加された最後の二つの理由は、より多くのコンテナ固有であり、両方の私の意見では非常に強力な引数です
ベン・アーロンソン

「新しいものの使用を避けてください」-また、静的コード分析を妨げるため、次のようなものを使用し始める必要があります:hmemcpy.github.io/AgentMulder。この段落で説明するその他の利点は、IoCではなくDIを使用することです。また、使用しない場合、クラスは引き続き結合されます newのますが、パラメーターのインターフェイスの代わりに具象型を使用します。
デン14

1
全体として、IoCは欠陥のないものとして描画されます。たとえば、大きな欠点については言及されていません。エラーのクラスをコンパイル時ではなくランタイムにプッシュします。
デン14

2

単一責任の原則によれば、すべてのクラスは単一の責任のみを持たなければなりません。クラスの新しいインスタンスを作成することは別の責任であるため、この種のコードを1つ以上のクラスにカプセル化する必要があります。ファクトリー、ビルダー、DIコンテナーなどの作成パターンを使用してそれを行うことができます。

制御の反転や依存関係の反転のような他の原則があります。このコンテキストでは、依存関係のインスタンス化に関連しています。彼らは、高レベルのクラスを、使用する低レベルのクラス(依存関係)から切り離す必要があると述べています。インターフェースを作成することで物事を分離できます。したがって、低レベルのクラスは特定のインターフェイスを実装する必要があり、高レベルのクラスはこれらのインターフェイスを実装するクラスのインスタンスを利用する必要があります。(注:REST統一インターフェース制約は、システムレベルで同じアプローチを適用します。)

例によるこれらの原則の組み合わせ(低品質のコードのため、申し訳ありませんが、C#の代わりにアドホック言語を使用しました。

  1. 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();
  2. 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();
  3. はい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();
  4. はい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を実装する別のものに置き換えることができるコードができました。参加するクラスは互いに分離されており、インターフェースのみを知っているため、これは良いことです。インスタンス化のコードが再利用可能であるという別の利点。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.