web-dev-qa-db-ja.com

依存性注入について

依存性注入 (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; 
}

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

115
MyDaftQuestions

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

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

106
Stefan Billiet

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 ただし、この実装は完全に使用できません不変オブジェクトの場合(この単純な実装では、コンストラクターのパラメーターを使用できません)。

52
amon

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

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

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

48
user967543

リビングルームでインテリアデザインをしているとします。天井に豪華なシャンデリアを設置し、上品なフロアランプを差し込みます。 1年後、あなたの素敵な妻は、彼女が少しフォーマルではない部屋を望んでいると判断しました。どの照明器具を変更する方が簡単ですか?

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

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

ところで、DIの議論は、Mark Seemanによる 。NETでの依存性注入 の推奨なしでは不完全です。 .NETに精通していて、大規模なソフトウェア開発に関与するつもりである場合、これは重要な資料です。彼は私よりもはるかによくコンセプトを説明しています。

あなたのコード例が見落としているDIの最後の重要な特徴を1つ残しておきます。 DIを使用すると、格言 "Program to interfaces"にカプセル化された柔軟性の大きな可能性を利用できます。例を少し変更するには:

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

しかし、今、MyRoomはILightFixtureインターフェースに合わせて設計されているため、来年に簡単に行ってMain関数の1行を変更できます(別の「依存関係」を「挿入」)し、MyRoomで新しい機能を即座に取得します(別のライブラリにある場合、MyRoomを再構築または再デプロイする必要はありません。これはもちろん、すべてのILightFixture実装がLiskov Substitutionを考慮して設計されています)。すべて、単純な変更のみで:

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

さらに、ユニットテストMyRoom(あなたユニットテストですよね?)を実行して、「模擬」照明器具を使用できます。 MyRoomを完全に独立してテストできるClassyChandelier.

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

18
kmote

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

(明らかに)オブジェクト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コンテナーの助けを借りて。

4
psr

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

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

onlyの利点は、コラボレーターをプッシュすることで、クライアントコードがクラスを使用して、現在のニーズに合ったオブジェクトグラフを作成できるようになります。 。共同作業者の具体的なタイプとライフサイクルを個人的に決定することにより、グラフの形状と変更可能性を任意に事前に決定していない。

(DIは当然インターフェースの使用を促進しますが、テスト容易性、疎結合などの他のすべての利点は、主にインターフェースの使用に由来し、依存性注入性にはそれほど依存しません)。

独自のコラボレーターのインスタンス化を回避する場合は、クラスがそのコラボレーターをコンストラクター、プロパティ、またはメソッドからしたがって取得する必要があることに注意してください-引数(ちなみに、この後者のオプションは見過ごされがちです...クラスの共同作業者がその「状態」の一部であることは必ずしも意味がありません)。

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

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

物事のクラスごとの見解についてはこれで終わりです「あなた自身のコラボレーターをインスタンス化しない」というルールに従ったたくさんのクラスがあり、それらからアプリケーションを作りたいとしましょう。最も簡単なことは、古き良きcode(コンストラクタ、プロパティ、およびメソッドを呼び出すための本当に便利なツールです!)を使用してオブジェクトグラフを作成することです。欲しい、それにいくつかの入力を投げます。 (はい、グラフ内のそれらのオブジェクトのいくつかは、それ自体がオブジェクトファクトリになります。これらは、共同作業者としてグラフ内の他の長期間有効なオブジェクトに渡され、サービスの準備ができています...すべてのオブジェクトを事前に構築することはできません! )。

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

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

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

そのタイプの最も極端な状況では、非常に一般的なアプローチを採用して、エンドユーザーにgeneraltheirのオブジェクトグラフを選択して許可するthemランタイムへのインターフェースの具体的な実現を提供します! (ドキュメントは光り輝く宝石であり、ユーザーは非常に賢く、少なくともアプリケーションのオブジェクトグラフの大まかな概要に精通していますが、コンパイラーは便利ではありません)。このシナリオは、いくつかの「エンタープライズ」状況で理論的に発生する可能性があります。

その場合、おそらく、ユーザーが任意の型を表現できる宣言型言語、そのような型のオブジェクトグラフの構成、および神話上のエンドユーザーが混合および照合できるインターフェイスのパレットがあるはずです。ユーザーの認知的負荷を軽減するには、「慣例による構成」アプローチを好むため、ユーザーは全体に取り組むのではなく、対象となるオブジェクトグラフフラグメントをステップインしてオーバーライドするだけで済みます。

あなたは貧しい芝生!

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

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

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

ブートノート

コードを使用してオブジェクトグラフを作成するタスクを省くコーダーを提供するGoogle Guiceを使用することはありません。コードを書くことによって。ああ!

4
David Bullock

多くの人がここでDIはインスタンス変数の初期化を外部委託するメカニズムであると述べました。

明白で単純な例では、「依存性注入」は実際には必要ありません。これは、質問したように、コンストラクターに渡す単純なパラメーターで行うことができます。

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

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

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

これらの種類のリソースについては、パラメーターリストを介してすべてを移動するのは意味がないため、DIが発明されました。

最後に、実際のインジェクションを実行するDIコンテナーも必要です(ここでも、呼び出し元と呼び出し先の両方を実装の詳細から解放します)。

0
gamliela