依存性注入について


109

私は依存性注入(DI)について読んでいます。私にとって、それは制御の反転(IoC)を参照していることを読んでいたので、行うのは非常に複雑なことであり、私は旅に出るつもりだと感じました。

これは私の理解です:それを消費するクラスでモデルを作成する代わりに、モデルを(すでに興味深いプロパティで満たされている)必要な場所に渡します(注入します)コンストラクター)。

私には、これは単に引数を渡すだけです。私はポイントを理解し損ねたに違いない?おそらく、より大きなプロジェクトでより明白になりますか?

私の理解は非DIです(擬似コードを使用):

public void Start()
{
    MyClass class = new MyClass();
}

...

public MyClass()
{
    this.MyInterface = new MyInterface(); 
}

DIは

public void Start()
{
    MyInterface myInterface = new MyInterface();
    MyClass class = new MyClass(myInterface);
}

...

public MyClass(MyInterface myInterface)
{
    this.MyInterface = myInterface; 
}

私はここで混乱していると確信しているので、誰かが光を当てることができますか?


5
5つのMyInterfaceとMyClassを使用し、複数のクラスで依存関係チェーンを形成してみてください。すべてをセットアップすることはお尻の痛みになります。
陶酔

159
私にとって、これはただ議論をしているだけです。私は要点を誤解したに違いありませんか?」わかった。それが「依存性注入」です。さあ、シンプルなコンセプトのために他にどんなクレイジーな用語を思いつくことができるか見てみましょう、楽しいです!
エリックリッパー

8
リッパート氏のコメントに十分賛成できない。私も、とにかく自然にやっていたことの専門用語を混乱させることがわかりました。
アレックスハンフリー

16
@EricLippert:正しい間、私はそれがわずかに還元的だと思います。Parameters-as-DIは、データをやり取りすることに慣れている平均的な命令型/ OOプログラマーにとって、すぐにわかりやすい概念ではありません。ここでDIを使用すると、動作を渡すことができます。これには、平凡なプログラマーが持つよりも柔軟な世界観が必要です。
Phoshi

14
@Phoshi:あなたは良い点を挙げていますが、そもそも「依存性注入」が良い考えだと私が懐疑的である理由にも指を置いています。別のクラスの動作の正確さとパフォーマンスに依存するクラスを作成する場合、最後に行うことは、依存関係を正しく構築および構成する責任をクラスのユーザーに与えることです!それが私の仕事です。消費者に依存関係を注入させるたびに、消費者が間違っている可能性のあるポイントを作成します。
エリックリッパー

回答:


106

ええ、そうです、あなたは依存関係を、コンストラクターを通して、またはプロパティを通して注入します。
この理由の1つは、MyInterfaceのインスタンスを構築する方法の詳細をMyClassに押し付けないことです。MyInterfaceは、それ自体で依存関係の全リストを持つものである可能性があり、MyClass内ですべてのMyInterface依存関係をインスタンス化すると、MyClassのコードは非常に高速になります。

別の理由はテストのためです。
ファイルリーダーインターフェイスに依存関係があり、ConsumerClassなどのコンストラクターを介してこの依存関係を注入する場合、テスト中に、ファイルリーダーのメモリ内実装をConsumerClassに渡すことができます。テスト中にI / Oを実行します。


13
これは素晴らしい、よく説明されています。私の担当者はまだ許可していないので、架空の+1を受け入れてください!
MyDaftQuestions 14年

11
複雑な構築シナリオは、Factoryパターンの使用に適しています。そのようにして、ファクトリーはオブジェクトを構築する責任を引き受けます。そのため、クラス自体は必要なく、単一の責任原則に固執します。
マットギブソン

1
@MattGibson良い点。
ステファンビリエット14年

2
+1をテストするために、適切に構造化されたDIは、テストを高速化し、テスト対象のクラスに含まれる単体テストを作成するのに役立ちます。
UmurKontacı14年

52

DIの実装方法は、使用する言語に大きく依存します。

以下に、DI以外の簡単な例を示します。

class Foo {
    private Bar bar;
    private Qux qux;

    public Foo() {
        bar = new Bar();
        qux = new Qux();
    }
}

たとえば、テストのためににモックオブジェクトを使用したいときは、これは面倒ですbar。したがって、これをより柔軟にし、コンストラクターを介してインスタンスを渡すことができます。

class Foo {
    private Bar bar;
    private Qux qux;

    public Foo(Bar bar, Qux qux) {
        this.bar = bar;
        this.qux = qux;
    }
}

// in production:
new Foo(new Bar(), new Qux());
// in test:
new Foo(new BarMock(), new Qux());

これは、すでに最も単純な形式の依存性注入です。しかし、すべては手動で実行する必要があるため(これは、呼び出し側が内部オブジェクトへの参照を保持し、状態を無効にすることができるため)、これでもいやです。

ファクトリーを使用することで、さらに抽象化を導入できます。

  • 1つのオプションはFoo、抽象ファクトリーによって生成されます

    interface FooFactory {
        public Foo makeFoo();
    }
    
    class ProductionFooFactory implements FooFactory {
        public Foo makeFoo() { return new Foo(new Bar(), new Baz()) }
    }
    
    class TestFooFactory implements FooFactory {
        public Foo makeFoo() { return new Foo(new BarMock(), new Baz()) }
    }
    
    FooFactory fac = ...; // depends on test or production
    Foo foo = fac.makeFoo();
  • 別のオプションは、ファクトリーをコンストラクターに渡すことです。

    interface DependencyManager {
        public Bar makeBar();
        public Qux makeQux();
    }
    class ProductionDM implements DependencyManager {
        public Bar makeBar() { return new Bar() }
        public Qux makeQux() { return new Qux() }
    }
    class TestDM implements DependencyManager {
        public Bar makeBar() { return new BarMock() }
        public Qux makeQux() { return new Qux() }
    }
    
    class Foo {
        private Bar bar;
        private Qux qux;
    
        public Foo(DependencyManager dm) {
            bar = dm.makeBar();
            qux = dm.makeQux();
        }
    }

これに関する残りの問題は、DependencyManager構成ごとに新しいサブクラスを作成する必要があること、および管理できる依存関係の数がかなり制限されていることです(新しい依存関係ごとにインターフェイスに新しいメソッドが必要です)。

リフレクションや動的クラスロードなどの機能を使用すると、これを回避できます。しかし、これは使用する言語に大きく依存します。Perlでは、クラスは名前で参照できます。

package Foo {
    use signatures;

    sub new($class, $dm) {
        return bless {
            bar => $dm->{bar}->new,
            qux => $dm->{qux}->new,
        } => $class;
    }
}

my $prod = { bar => 'My::Bar', qux => 'My::Qux' };
my $test = { bar => 'BarMock', qux => 'QuxMock' };
$test->{bar} = 'OtherBarMock';  # change conf at runtime

my $foo = Foo->new(rand > 0.5 ? $prod : $test);

Javaのような言語では、依存関係マネージャーを次のように動作させることができますMap<Class, Object>

Bar bar = dm.make(Bar.class);

どの実際のクラスにBar.class解決されるかは、たとえば、Map<Class, Class>インターフェイスを実装にマッピングするを維持することにより、実行時に構成できます。

Map<Class, Class> dependencies = ...;

public <T> T make(Class<T> c) throws ... {
    // plus a lot more error checking...
    return dependencies.get(c).newInstance();
}

コンストラクターの記述には、まだ手動要素が関係しています。しかし、アノテーションを介してDIを駆動するなどして、コンストラクターを完全に不要にすることができます。

class Foo {
    @Inject(Bar.class)
    private Bar bar;

    @Inject(Qux.class)
    private Qux qux;

    ...
}

dm.make(Foo.class);  // takes care of initializing "bar" and "qux"

:ここで小さな(そして非常に制限された)DIフレームワークの実装例であるhttp://ideone.com/b2ubuFこの実装は不変オブジェクトの完全に使用不能であるが(このナイーブな実装はコンストラクタのパラメータを取ることができません)。


7
これは素晴らしい例ですが、すべてを消化するために数回読む必要がありますが、非常に多くの時間を割いていただきありがとうございます。
MyDaftQuestions 14年

33
次の単純化がより複雑になるのは楽しいです。
Kromster

48

Stack Overflowの友人は、これに対する良い答えを持っています。私のお気に入りは、引用を含む2番目の回答です。

「依存性注入」は、5セントの概念を表す25ドルの用語です。(...)依存性注入とは、オブジェクトにインスタンス変数を与えることです。(...)。

ジェームズ・ショアのブログ。もちろん、この上に階層化されたより複雑なバージョン/パターンがありますが、何が起こっているかを基本的に理解するのに十分です。オブジェクトが独自のインスタンス変数を作成する代わりに、それらは外部から渡されます。


10
私はこれを読みました...そして今まで、それは意味がありませんでした...今、それは理にかなっています。依存関係は、実際にはそれほど大きな概念ではありません(どの程度強力であるかに関係なく)が、大きな名前とそれに関連する多くのインターネットノイズがあります!
MyDaftQuestions 14年

18

あなたがあなたのリビングルームでインテリアデザインをしているとしましょう:あなたはあなたの天井に派手なシャンデリアをインストールし、上品なマッチングフロアランプを差し込みます。1年後、あなたの素敵な妻は、部屋を少し控えめにしたいと決めました。どの照明器具が変更しやすくなりますか?

DIの背後にある考え方は、どこでも「プラグイン」アプローチを使用することです。乾式壁がなく、すべての電線が露出している単純な小屋に住んでいる場合、これは大したことではないように思えるかもしれません。(あなたは便利屋です-何でも変更できます!)同様に、小さくてシンプルなアプリケーションでは、DIは価値があるよりも多くの複雑さを追加できます。

しかし、大規模で複雑なアプリケーションの場合、DIは長期的なメンテナンスに不可欠です。コード全体(たとえば、データベースバックエンド)からモジュール全体を「取り外し」、同じことを達成するがまったく異なる方法(クラウドストレージシステムなど)で実行する異なるモジュールと交換できます。

ところで、DIの議論は、Mark Seemanによる.NETでのDependency Injectionの推奨なしでは不完全です。.NETに精通しており、大規模なSW開発に関与する予定がある場合、これは重要な資料です。彼は私よりもはるかに良いコンセプトを説明しています。

あなたのコード例を見落とすDIの最後の重要な特徴を残しておきましょう。DIを使用すると、「プログラムからインターフェイス」という格言にカプセル化された柔軟性の大きな可能性を活用できます。例を少し変更するには:

public void Main()
{
    ILightFixture fixture = new ClassyChandelier();
    MyRoom room = new MyRoom (fixture);
}
...
public MyRoom(ILightFixture fixture)
{
    this.MyLightFixture = fixture ; 
}

しかし今、MyRoomILightFixture インターフェース用に設計されているため、来年簡単にメイン関数の1行を変更し(別の「依存関係」を「注入」)、MyRoomで新しい機能をすぐに取得できます( MyRoomが別のライブラリにある場合、MyRoomを再構築または再デプロイします。もちろん、これはすべてのILightFixture実装がLiskov Substitutionを念頭に置いて設計されていることを前提としています。すべて、簡単な変更だけで:

public void Main()
{
    ILightFixture fixture = new MuchLessFormalLightFixture();
    MyRoom room = new MyRoom (fixture);
}

プラス、私は今ユニットテストすることができますMyRoom(あなたがいるね、ユニットテスト)とは、私がテストすることができます「モック」照明器具、使用MyRoomの完全に独立しClassyChandelier.

もちろん、もっと多くの利点がありますが、これらは私にアイデアを売ったものです。


感謝したいと思いましたが、これは私にとって有益でしたが、彼らはあなたにそれを望んではいませんが、改善を提案するためにコメントを使用してください。しかし、そうでなければ、おかげで、これは私にとって役に立ちました。
スティーブ

2
また、あなたが参照した本にも興味がありますが、このテーマに関する584ページの本があることを警告します。
スティーブ

4

依存性注入は単にパラメーターを渡すだけですが、それでもいくつかの問題につながります。名前は、オブジェクトの作成と渡すことの違いが重要である理由に少し焦点を当てることを意図しています。

オブジェクトAがタイプBを呼び出すときはいつでも(明らかに)AはBの動作に依存するため、重要です。つまり、Aが具体的なBのどのタイプを使用するかを決定すると、Aを変更せずに外部クラスでAを使用する方法の柔軟性が失われます。

これは、依存性注入の「見逃し」について話しているからです。これは、人々がそれを好む理由の多くのポイントです。パラメーターを渡すだけの場合もありますが、そうするかどうかは重要です。

また、実際にDIを実装する際の困難の一部は、大規模なプロジェクトでよりよく現れます。通常、使用する具体的なクラスの実際の決定を一番上に移動するため、startメソッドは次のようになります。

public void Start()
{
    MySubSubInterfaceA mySubSubInterfaceA = new mySubSubInterfaceA();
    MySubSubInterfaceB mySubSubInterfaceB = new mySubSubInterfaceB();     
    MySubInterface mySubInterface = new MySubInterface(mySubSubInterfaceA,mySubSubInterfaceB);
    MyInterface myInterface = new MyInterface(MySubInterface);
    MyClass class = new MyClass(myInterface);
}

しかし、この種のリベットコードの別の400行を使用します。時間の経過とともにそれを維持することを想像すると、DIコンテナの魅力がより明確になります。

また、IDisposableを実装するクラスを想像してください。それを使用するクラスがそれ自体を作成するのではなく、インジェクションを介してそれを取得する場合、Dispose()を呼び出すタイミングをどのように知るのですか?(これは、DIコンテナーが扱う別の問題です。)

確かに、依存性注入は単にパラメーターを渡すだけですが、実際に依存性注入と言うときは、「オブジェクトにパラメーターを渡す代わりに、オブジェクトが何かをインスタンス化する代替手段」を意味し、おそらくDIコンテナーの助けを借りて、このようなアプローチ。


それはたくさんのサブとインターフェースです!インターフェースを実装するクラスではなく、インターフェースを新しくするつもりですか?間違っているようです。
JBRWilkinson

@JBRWilkinson私は、インターフェースを実装するクラスを意味します。私はそれを明確にする必要があります。しかし、ポイントは、大きなアプリには依存関係を持つクラスがあり、そのクラスには依存関係があるということです... Mainメソッドですべてを手動で設定することは、多くのコンストラクター注入を行った結果です。
psr

4

クラスレベルでは簡単です。

「ディペンデンシーインジェクション」は、「コラボレーターをどのように見つけますか」という質問に答えます。「彼らはあなたにプッシュされます-自分で行ってもらう必要はありません」。(「制御の反転」に似ていますが、「入力の操作をどのように順序付けますか?」という質問に同様の答えがあります)。

コラボレーターをプッシュすることの唯一の利点は、クライアントコードがクラスを使用して、現在のニーズに合ったオブジェクトグラフを作成できることです。協力者の具体的なタイプとライフサイクル。

(テスト可能性、疎結合などの他のすべての利点は、主にインターフェイスの使用によるものであり、依存関係の注入によるものではありませんが、DIは当然インターフェイスの使用を促進します)。

あなたがあなた自身のコラボレーターをインスタンス化しないようならば、あなたのクラスがしなければならない、ということは注目に値するので、それはありません...(この後者のオプションは、多くの場合、途中で、見過ごされているコンストラクタ、プロパティ、またはメソッドの引数からその協力者を得ますクラスの共同作業者がその「状態」の一部であるとは必ずしも意味がありません)。

そしてそれは良いことです。

アプリケーションレベルで...

クラスごとの物事の見方についてはこれで終わりです。「自分のコラボレーターをインスタンス化しない」というルールに従って、クラスからアプリケーションを作成したいというクラスがたくさんあるとしましょう。最も簡単なことは、古き良きコード(コンストラクタ、プロパティ、メソッドを呼び出すための本当に便利なツール!)を使用して、目的のオブジェクトグラフを作成し、そこに入力を投げることです。はい)。

...アプリのオブジェクトグラフを「柔軟に」構成する必要性...

他の(コードに関連しない)目的に応じて、展開されたオブジェクトグラフをエンドユーザーに制御させたい場合があります。これにより、名前/値のペアを含む独自のデザインのテキストファイル、XMLファイル、カスタムDSL、宣言型のグラフ記述言語など、構成スキームの方向性が導かれます。 YAML、JavaScriptなどの命令型スクリプト言語、または手元のタスクに適した何か。ユーザーのニーズを満たす方法で、有効なオブジェクトグラフを作成するために必要なものは何でも。

...かなりの設計力になる可能性があります。

最も極端なそのタイプの状況、あなたは非常に一般的なアプローチを取ることを選択し、エンドユーザーに与えることができる一般のオブジェクト・グラフ「アップ配線」のメカニズム彼らが選んだのも許す彼らはへのインターフェースの具体的な実現を提供するために、ランタイム!(あなたのドキュメントはキラキラ光る宝石であり、ユーザーは非常に頭が良く、少なくともアプリケーションのオブジェクトグラフの大まかな概要に精通していますが、たまたまコンパイラが手元にないようにしてください)。このシナリオは、いくつかの「エンタープライズ」状況で理論的に発生する可能性があります。

その場合は、おそらく、ユーザーが任意の型、そのような型のオブジェクトグラフの構成、および神話上のエンドユーザーがミックスアンドマッチできるインターフェイスのパレットを表現できる宣言型言語があります。ユーザーの認知的負荷を軽減するには、「慣例による構成」アプローチを選択します。これにより、ユーザーは、すべてに取り組むのではなく、関心のあるオブジェクトグラフフラグメントに介入してオーバーライドするだけで済みます。

あなたは貧しい芝です!

自分ですべてを書きたくはなかったので(ただし、真剣に、言語のYAMLバインディングをチェックしてください)、何らかのDIフレームワークを使用しています。

そのフレームワークの成熟度によっては、それが理にかなっている場合でも(オブジェクトの存続期間中にコラボレーターが変更しない)、コンストラクター注入を使用するオプションがない場合があります。共同作業者はオブジェクトの存続期間中に変更されません。また、インターフェイスのすべての具体的な実装に特定のタイプの共同作業者が必要な論理的理由がない場合でも)。もしそうなら、あなたはコードベース全体で熱心に「使用されたインターフェース」を持っているにもかかわらず、あなたは現在強結合地獄にいます-恐怖!

ただし、コンストラクタインジェクションのオプションを提供するDIフレームワークを使用したことを願っています。ユーザーは、構成に必要な特定のことを考えず、タスクに適したUIを提供することに時間を費やさないので、少し不機嫌です。手元に。(公平ではありますが、おそらくあなたは方法を考えようとしましたが、JavaEEはあなたを失望させ、この恐ろしいハックに頼らなければなりませんでした)。

ブートノート

Google Guiceを使用することは決してありません。GoogleGuiceを使用すると、コーダーはコードを記述してオブジェクトグラフをコードで作成する作業を省くことができます。ああ!


0

多くの人がここで、DIはインスタンス変数の初期化をアウトソースするメカニズムであると言いました。

明白で簡単な例では、「依存性注入」は本当に必要ありません。これを行うには、コンストラクターに渡す単純なパラメーターを使用します。これは、上記で説明したとおりです。

ただし、アプリケーションレベルのリソースについて話すときは、専用の「依存性注入」メカニズムが役立つと思います。アプリケーションレベルのリソースでは、呼び出し元と呼び出し先の両方がこれらのリソースについて何も知らない(そして知りたくない!)

例:データベース接続、セキュリティコンテキスト、ロギング機能、構成ファイルなど。

さらに、一般に、すべてのシングルトンがDIの良い候補です。使用して使用するものが多いほど、DIが必要になる可能性が高くなります。

これらの種類のリソースでは、パラメーターリストを介してそれらを移動することは意味がないことは明らかであり、したがってDIが発明されました。

最後の注意点として、実際のインジェクションを行うDIコンテナも必要です(もう一度、実装の詳細から呼び出し元と呼び出し先の両方を解放するため)。


-2

抽象参照型を渡すと、呼び出しコードは抽象型を拡張/実装するオブジェクトを渡すことができます。そのため、将来、オブジェクトを使用するクラスは、コードを変更することなく作成された新しいオブジェクトを使用する可能性があります。


-4

これは、アモンの答えに加えて別のオプションです

ビルダーを使用:

クラスFoo {
    プライベートバーバー;
    プライベートQux qux;

    private Foo(){
    }

    public Foo(Builder builder){
        this.bar = builder.bar;
        this.qux = builder.qux;
    }

    public static class Builder {
        プライベートバーバー;
        プライベートQux qux;
        public Buider setBar(Bar bar){
            this.bar = bar;
            これを返す;
        }
        公開ビルダーsetQux(Qux qux){
            this.qux = qux;
            これを返す;
        }
        public Foo build(){
            return new Foo(this);
        }
    }
}

// 生産中:
Foo foo = new Foo.Builder()
    .setBar(new Bar())
    .setQux(新しいQux())
    .build();

//テスト中:
Foo testFoo = new Foo.Builder()
    .setBar(new BarMock())
    .setQux(新しいQux())
    .build();

これは依存関係に使用するものです。DIフレームワークは使用していません。彼らは邪魔をします。


4
これは1年前の他の回答にあまり追加されず、ビルダーが依存関係の注入を理解するために質問者に役立つ理由を説明しません。

@Snowman DIの代わりの別のオプションです。ごめんなさい、あなたはそのように見えません。反対票をいただきありがとうございます。
user3155341

1
質問者は、DIが本当にパラメーターを渡すのと同じくらい簡単かどうかを理解したかったため、DIの代替を求めていませんでした。

1
OPの例は非常に優れています。あなたの例は、単純なアイデアであるものに対してより複雑なものを提供します。彼らは質問の問題を説明していません。
アダムザッカーマン

コンストラクターDIはパラメーターを渡します。具体的なクラスオブジェクトではなく、インターフェイスオブジェクトを渡す場合を除きます。DIを介して解決されるのは、この具体的な実装への「依存」です。コンストラクターは、渡された型を知らず、気にしません。インターフェースを実装する型を受け取っていることを知っているだけです。PluralSightをお勧めします。初心者向けのDI / IOCコースがあります。
-Hardgraf
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.