依存性注入とは何ですか?


3075

依存関係注入についての特定の質問がすでに投稿されている質問がいくつかあります。たとえば、いつ使用するか、どのフレームワークがあるかなどです。しかしながら、

依存性注入とは何ですか?いつ/なぜ使用する必要があるのですか?


ここでの依存性注入に関する私の議論を参照してください。
ケビンS.

33
リンクに関するコメントに同意します。他の人を紹介したいと思うかもしれません。しかし、少なくともそれらをリンクしている理由と、このリンクがGoogleを使用して取得できる他のリンクよりも優れている理由を追加してください
Christian Payne

@AR:技術的には、Dependency InjectionはIoCの特別な形式ではありません。むしろ、IoCは、依存性注入を提供するために使用される1つの手法です。他の手法を使用して依存性注入を提供することもできます(ただし、IoCが一般的に使用される唯一のものです)。IoCは他の多くの問題にも使用されます。
Sean Reilly、

私がDIについて今まで読んだ中で最も素晴らしい説明の1つは、GoogleのGuice(ジュースと発音)からですhttp://code.google.com/p/google-guice/wiki/Motivation?tm=6
Raj

136
リンクに関しては、それらはしばしば何らかの形で消えることを覚えておいてください。SOの回答にはデッドリンクの数が増えています。ですから、リンクされた記事がどれほど良いものであっても、それを見つけることができなくてもまったく意味がありません。
DOK

回答:


1931

Dependency Injectionは、他のオブジェクトまたはフレームワーク(Dependency Injector)に依存関係を渡します

依存性注入により、テストが容易になります。注入は、コンストラクタを介して行うことができます。

SomeClass() 次のようなコンストラクタがあります:

public SomeClass() {
    myObject = Factory.getObject();
}

問題myObjectディスクアクセスやネットワークアクセスなどの複雑なタスクが含まれる場合、でユニットテストを実行するのは困難SomeClass()です。プログラマはモックする必要がmyObjectあり、ファクトリコールを傍受する可能性あります。

代替ソリューション

  • myObjectコンストラクタに引数として渡す
public SomeClass (MyClass myObject) {
    this.myObject = myObject;
}

myObject 直接渡すことができるため、テストが容易になります。

  • 一般的な代替策の1つは、何もしないコンストラクタを定義することです。依存性注入は、セッターを介して行うことができます。(h / t @MikeVella)。
  • Martin Fowlerは、3番目の代替(h / t @MarcDix)を文書化しています。このクラスでは、プログラマーが挿入したいと思っている依存関係のインターフェース明示的に実装しています

依存性の注入なしに単体テストでコンポーネントを分離することは困難です。

2013年にこの回答を書いたとき、これはGoogle Testing Blogの大きなテーマでした。プログラマーがランタイム設計に追加の柔軟性を常に必要とするわけではないため(たとえば、サービスロケーターや同様のパターンの場合)、それは私にとって最大の利点です。多くの場合、プログラマーはテスト中にクラスを分離する必要があります。


25
マーティンファウラーの記事へのベンホフスタインの言及が主題についての「必読」を指摘するものとして必要であることを認めて、私は実際にSOの質問に答えるので、私はwdsの答えを受け入れます。
AR。

121
説明と動機付けのための+1:クラスが他の誰かの問題に依存するオブジェクトの作成を行う。別の言い方をすると、DIはクラスをよりまとまりのあるものにします(それらはより少ない責任を持ちます)。
Fuhrmanator、2012年

13
依存関係は「コンストラクターに渡される」と言いますが、私が理解しているように、これは厳密には当てはまりません。オブジェクトがインスタンス化された後で依存関係がプロパティとして設定されている場合でも、依存関係の注入です。
Mike Vella 2013

1
@MikeVellaはい、そうです。ほとんどの場合、実際には違いはありませんが、プロパティは一般に少し柔軟です。私はそれを指摘するためにテキストを少し編集します。
wds 2013

2
これまでに見つけた最良の回答の1つなので、それを改善することに本当に興味があります。依存性注入の3番目の形式であるインターフェイス注入の説明がありません。
マークディックス

2351

これまでに見つけた最良の定義は、James Shoreによるものです。

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

あるMartin Fowler氏による記事も有用であろう。

依存性注入は、基本的に、オブジェクト自体を構築するのではなく、オブジェクトが必要とするオブジェクト(その依存関係)を提供します。依存関係のモックやスタブを作成できるため、テストに非常に役立つテクニックです。

依存関係は、さまざまな方法でオブジェクトに注入できます(コンストラクター注入やセッター注入など)。専用の依存関係注入フレームワーク(Springなど)を使用してそれを行うこともできますが、必須ではありません。依存性注入を行うためにこれらのフレームワークは必要ありません。オブジェクト(依存関係)を明示的にインスタンス化して渡すことは、フレームワークによる注入と同じくらい優れた注入です。


35
Jamesの記事の説明、特に最後の部分が好きです。「それでも、3つの概念(「TripPlanner」、「CabAgency」、「AirlineAgency」)を取るアプローチには驚かされ、それらを9つ以上のクラスに変える必要があります。その後、1行のアプリケーションロジックが記述される前に、数十行のグルーコードと構成XMLを追加します。」これは私が頻繁に(悲しいことに)目にしたことです-その依存性注入(それ自体は彼によって説明されているように良い)は誤用されて、もっと簡単にできることをやりすぎました-最終的に「サポート」コードを書くことになる
Matt

2
Re:「オブジェクト(依存関係)を明示的にインスタンス化して渡すことは、フレームワークによる注入と同じくらい優れた注入です。」では、なぜ人々はそれを行うフレームワークを作ったのでしょうか?
dzieciou

13
すべてのフレームワークが作成される(または少なくとも作成される必要がある)のと同じ理由で、特定の複雑さに達したときに作成する必要のある反復/定型コードが多数あるためです。問題は、フレームワークが厳密に必要とされていない場合でも、フレームワークに手を伸ばすことがよくあることです。
Thiago Arrais、2016年

14
5セントコンセプトの25ドルの条件はまだ実現していません。ここでの私を助けたことを良い記事があります:codeproject.com/Articles/615139/...
クリスティン

@Matt構成ファイルは、「決定を延期する」ことを極端にした「実際の実行時まで決定を延期する」にすぎません。Dagger、特にDaggerは、私の見解では、「アプリケーションのアセンブリ時まで決定を延期する」というスイートスポットを見つけました。
–ThorbjørnRavn Andersen 2017

645

私は疎結合の観点からこの面白い例を見つけました:

どのアプリケーションも、相互に連携していくつかの便利なことを実行する多くのオブジェクトで構成されています。従来、各オブジェクトは、連携する依存オブジェクト(依存関係)への独自の参照を取得する責任があります。これは、高度に結合されたクラスとテストが難しいコードにつながります。

たとえば、Carオブジェクトを考えてみましょう。

A Carは、実行する車輪、エンジン、燃料、バッテリーなどに依存します。従来、このような依存オブジェクトのブランドは、Carオブジェクトの定義とともに定義されていました。

依存性注入なし(DI):

class Car{
  private Wheel wh = new NepaliRubberWheel();
  private Battery bt = new ExcideBattery();

  //The rest
}

ここで、Carオブジェクトは依存オブジェクトの作成を担当します。

Wheel最初のNepaliRubberWheel()パンクチャ後に、依存オブジェクトのタイプを変更したい場合はどうでしょうか。新しい依存性sayを使用してCarオブジェクトを再作成する必要がありますがChineseRubberWheel()、これCarを行うことができるのは製造元のみです。

それでDependency Injection私たちに何をしますか...?

依存性注入を使用する場合、オブジェクトには、コンパイル時(自動車の製造時)ではなく、実行時に依存関係が与えられます。これで、Wheelいつでも好きなときに変更できるようになりました。ここでは、実行時にdependencywheel)を注入できますCar

依存性注入を使用した後:

ここで、我々はされている注入依存関係、実行時に(ホイールとバッテリーを)。したがって、用語:依存性注入。

class Car{
  private Wheel wh; // Inject an Instance of Wheel (dependency of car) at runtime
  private Battery bt; // Inject an Instance of Battery (dependency of car) at runtime
  Car(Wheel wh,Battery bt) {
      this.wh = wh;
      this.bt = bt;
  }
  //Or we can have setters
  void setWheel(Wheel wh) {
      this.wh = wh;
  }
}

出典:依存性注入について


20
これを理解する方法は、新しいオブジェクトを別のオブジェクトの一部としてインスタンス化する代わりに、必要に応じてそのオブジェクトを挿入して、最初のオブジェクトの依存関係を削除することです。そうですか?
JeliBeanMachine 2014年

私はこれをここのコーヒーショップの例で説明しました:digigene.com/design-patterns/dependency-injection-coffeeshop
Ali Nem

11
単純なアナロジーを使用した単純な英語なので、このアナロジーは本当に好きです。評判の良いタイヤの生産を既存がある場合は、なぜ私はタイヤ製造部門、すなわちに作るためにゼロからスタートする必要があり、私は、ラインを組み立てる転がり落ちるまで設計から車を作ることにあまりにも多くの財政過ごしたマンパワーすでに、トヨタだと言うnewAをタイヤ?私はしません。私がしなければならないのは、それらから購入し(パラメーターを介して注入)、インストールして、ワラです!したがって、プログラミングに戻って、C#プロジェクトで既存のライブラリ/クラスを使用する必要があるとしましょう。実行/デバッグする方法は2つあります
。1-

(contt)、..外部ライブラリ/クラス、またはDLLから2を追加。この外部クラスの内容を確認する必要がない限り、DLLとして追加する方が簡単です。したがって、オプション1はnewそれを行い、オプション2はそれをパラメーターとして渡します。正確ではないかもしれませんが、理解しやすい単純な愚かです。
Jeb50 2017

1
@JeliBeanMachine(コメントへの返信が非常に遅いため申し訳ありません。)最初のオブジェクトのwheelオブジェクトまたはバッテリーオブジェクトへの依存関係を削除するのではなく、依存関係を渡して、インスタンスまたはインスタンスの実装を変更できるようにします依存。以前:CarはNepaliRubberWheelにハードコードされた依存関係を持っています。後:車は、ホイールのインスタンスに注入された依存関係を持っています。
Mikael Ohlson、2018年

263

依存性注入は、オブジェクトを内部で構築するのではなく、他のコードからオブジェクトのインスタンスを受け取るようにオブジェクトを設計する方法です。つまり、オブジェクトに必要なインターフェイスを実装するオブジェクトは、コードを変更せずに置き換えることができるため、テストが簡略化され、デカップリングが改善されます。

たとえば、次の例を考えてみます。

public class PersonService {
  public void addManager( Person employee, Person newManager ) { ... }
  public void removeManager( Person employee, Person oldManager ) { ... }
  public Group getGroupByManager( Person manager ) { ... }
}

public class GroupMembershipService() {
  public void addPersonToGroup( Person person, Group group ) { ... }
  public void removePersonFromGroup( Person person, Group group ) { ... }
} 

この例では、の実装PersonService::addManagerとは、PersonService::removeManagerインスタンスが必要になりGroupMembershipService、その作業を行うために。Dependency Injection を使用しない場合、これを行う従来の方法はGroupMembershipService、コンストラクタのnewをインスタンス化し、PersonService両方の関数でそのインスタンス属性を使用することです。ただし、のコンストラクタにGroupMembershipService必要なものが複数ある場合、またはさらに悪いことに、GroupMembershipServiceで呼び出す必要があるいくつかの初期化「セッター」があり、コードがかなり急速に成長し、PersonService現在はに依存するだけでGroupMembershipServiceなく、GroupMembershipService依存します。さらに、へのリンケージGroupMembershipServiceはにハードコーディングされてPersonServiceいるため、「ダミー」にすることはできません。GroupMembershipService テスト目的、またはアプリケーションのさまざまな部分で戦略パターンを使用するため。

Dependency Injectionを使用すると、GroupMembershipService内でをインスタンス化する代わりPersonServiceに、PersonServiceコンストラクターに渡すか、プロパティ(ゲッターとセッター)を追加して、そのローカルインスタンスを設定します。つまり、PersonServiceを作成する方法について心配する必要がなくなりGroupMembershipService、指定されたものを受け入れて、それらを使用するだけです。これはまたのサブクラスであるものを意味しGroupMembershipService、または実装は、GroupMembershipServiceインタフェースがに「を注入」することができPersonService、およびPersonService変更について知る必要はありません。


29
DIを使用した後に同じコード例を示すことができればすばらしいでしょう
CodyBugstein

170

受け入れられた答えは良いものですが、DIは、コード内でハードコードされた定数を回避する従来の方法と非常によく似ていることを付け加えておきます。

データベース名のような定数を使用する場合、コード内から定数ファイルにすばやく移動し、その値を含む変数を必要な場所に渡します。その理由は、これらの定数は通常、コードの他の部分よりも頻繁に変更されるためです。たとえば、テストデータベースでコードをテストする場合などです。

オブジェクト指向プログラミングの世界では、DIはこれに似ています。定数リテラルの代わりにそこにある値はオブジェクト全体ですが、それらを作成するコードをクラスコードから移動する理由は同様です。オブジェクトは、それらを使用するコードよりも頻繁に変更されます。このような変更が必要な重要なケースの1つはテストです。


18
+1「オブジェクトは、オブジェクトを使用するコードよりも頻繁に変更されます」。一般化するには、フラックスのポイントに間接参照を追加します。変化のポイントに応じて、間接参照は異なる名前で呼び出されます!!
Chethan

139

CarクラスとEngineクラスで簡単な例を試してみましょう。少なくとも今のところ、どの車でもどこにでもエンジンが必要です。したがって、依存関係の注入なしでコードがどのように見えるかを以下に示します。

public class Car
{
    public Car()
    {
        GasEngine engine = new GasEngine();
        engine.Start();
    }
}

public class GasEngine
{
    public void Start()
    {
        Console.WriteLine("I use gas as my fuel!");
    }
}

そして、Carクラスをインスタンス化するために、次のコードを使用します。

Car car = new Car();

GasEngineと密結合しているこのコードの問題。これをElectricityEngineに変更する場合は、Carクラスを書き直す必要があります。また、アプリケーションが大きくなるほど、新しいタイプのエンジンを追加して使用する必要がある問題と頭痛が増えます。

つまり、このアプローチでは、高レベルのCarクラスが、SOLIDの依存関係逆転原理(DIP)に違反する低レベルのGasEngineクラスに依存しているということです。DIPは、具象クラスではなく、抽象化に依存する必要があることを示唆しています。これを満足させるために、IEngineインターフェイスを導入し、以下のようにコードを書き直します。

    public interface IEngine
    {
        void Start();
    }

    public class GasEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I use gas as my fuel!");
        }
    }

    public class ElectricityEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I am electrocar");
        }
    }

    public class Car
    {
        private readonly IEngine _engine;
        public Car(IEngine engine)
        {
            _engine = engine;
        }

        public void Run()
        {
            _engine.Start();
        }
    }

これで、Carクラスはエンジンの特定の実装ではなく、IEngineインターフェイスのみに依存します。さて、唯一の秘訣は、どのようにしてCarのインスタンスを作成し、それにGasEngineやElectricityEngineのような実際の具体的なEngineクラスを与えるかです。そこで登場するのがDependency Injectionです。

   Car gasCar = new Car(new GasEngine());
   gasCar.Run();
   Car electroCar = new Car(new ElectricityEngine());
   electroCar.Run();

ここでは、基本的に、依存関係(エンジンインスタンス)をCarコンストラクターに注入(パス)します。これで、クラスのオブジェクトとその依存関係が疎結合になり、Carクラスを変更せずに新しいタイプのエンジンを簡単に追加できるようになりました。

Dependency Injectionの主な利点は、クラスにハードコーディングされた依存関係がないため、クラスがより疎結合になることです。これは、前述の依存関係の逆転の原則に従います。特定の実装を参照する代わりに、クラスは、クラスの構築時に提供される抽象化(通常はインターフェイス)を要求します

したがって、結局のところ、依存性注入は、オブジェクトとその依存関係の間の疎結合を実現するための手法にすぎません。クラスがそのアクションを実行するために必要とする依存関係を直接インスタンス化するのではなく、依存関係はコンストラクター注入によってクラスに(ほとんどの場合)提供されます。

また、多くの依存関係がある場合、すべての依存関係のどの具体的な実装にどのインターフェイスをマップする必要があるかを通知でき、構築時にこれらの依存関係を解決させることができるInversion of Control(IoC)コンテナーを使用することは非常に良い習慣です私たちのオブジェクト。たとえば、IoCコンテナのマッピングで、IEngine依存関係をGasEngineクラスにマッピングするように指定できます。IoCコンテナにCarクラスのインスタンスを要求すると、GasEngine依存関係を持つCarクラスが自動的に構築されます。渡された。

更新: Julie LermanのEF Coreに関するコースを最近視聴し、DIについての彼女の短い定義も気に入りました。

依存性注入とは、アプリケーションがオブジェクトをその場で必要なクラスに注入できるようにするためのパターンであり、それらのクラスにオブジェクトの責任を負わせる必要はありません。これにより、コードをより疎結合にし、Entity Framework Coreをこの同じサービスシステムにプラグインできます。


2
好奇心から、これは戦略パターンとどう違うのですか?このパターンはアルゴリズムをカプセル化し、それらを交換可能にします。依存性注入と戦略パターンは非常に似ているように感じます。
エリクシル2018年

110

あなたが釣りに行きたいと想像しましょう:

  • 依存性注入なしでは、すべてを自分で処理する必要があります。ボートを見つけたり、釣り竿を買ったり、餌を探したりする必要があります。もちろん可能ですが、それはあなたに多くの責任を負わせます。ソフトウェアの用語では、それはあなたがこれらすべてのもののルックアップを実行しなければならないことを意味します。

  • 依存関係注入により、他の誰かがすべての準備を担当し、必要な機器を利用できるようにします。ボート、釣り竿、餌が届きます(「注射される」)。すべてすぐに使用できます。


59
逆に、浴室をやり直すために配管工を雇ったとします。浴室をやり直すと、「素晴らしい、ここに私に手に入れてもらうために必要な道具と材料のリストがあります」と言われます。それは配管工の仕事ではないでしょうか?
jscs 2013年

誰かが誰かの面倒を見る必要があるように、ビジネスのことは知りませんが、それでもボート、スティック、ベイトのリストを収集することにします。
Chookoos 2013年

22
@JoshCaswellいいえ、それは配管工の使用者の仕事でしょう。クライアントとして、配管を行う必要があります。そのためには配管工が必要です。配管工はそれを必要としています。それらを得るために、それは配管会社によって装備されます。クライアントとして、配管工が正確に何をする必要があるのか​​知りたくありません。配管工として必要なものはわかっていますが、すべてを手に入れるのではなく、仕事をしたいだけです。配管工の雇用主は、配管工に必要なものを用意してから、人々の家に送る責任があります。
サラ

@kaiわかりました。ソフトウェアでは、工場について話していますよね?ただし、DIは通常、クラスがファクトリーを使用しないことも意味します。ファクトリーはまだ注入されないためです。あなたは顧客であり、雇用主(工場)に連絡してツールを提供する必要があるため、配管工に渡ることができます。それは実際にプログラムでどのように機能するのではないですか?したがって、顧客(呼び出しクラス/関数/何でも)はツールを調達する必要はありませんが、それでも仲介者である必要があり、雇用主(工場)から配管工(注入クラス)に到達するようにします。
KingOfAllTrades

1
@KingOfAllTrades:もちろん、いつか誰かが配管工を雇ったり装備したりする必要があるか、配管工がいません。しかし、あなたはそれをしている顧客を持っていません。顧客は配管工を要求するだけで、仕事に必要なものをすでに装備しています。DIを使用しても、最終的には依存関係を満たすためのコードがいくつか残ります。しかし、実際の作業を行うコードから分離しています。それを最大限に活用する場合、オブジェクトは依存関係を認識させるだけであり、オブジェクトグラフの構築は外部で、多くの場合initコードで行われます。
cHao 2017

102

これは、依存性注入依存性注入コンテナに関する最も簡単な説明です。

依存性注入なし

  • アプリケーションにはFoo(コントローラーなど)が必要なので、次のようにします。
  • アプリケーションがFooを作成する
  • アプリケーションがFooを呼び出す
    • Fooにはバー(サービスなど)が必要なので、次のようにします。
    • フーはバーを作成します
    • フーはバーを呼ぶ
      • BarにはBim(サービス、リポジトリなど)が必要なので、次のようにします。
      • バーがビムを作成
      • バーは何かをします

依存性注入あり

  • アプリケーションにはFoo、Bar、Bimが必要なので、次のようにします。
  • アプリケーションがBimを作成する
  • アプリケーションはバーを作成し、それにBimを与えます
  • アプリケーションがFooを作成し、それにバーを与える
  • アプリケーションがFooを呼び出す
    • フーはバーを呼ぶ
      • バーは何かをします

依存関係注入コンテナーの使用

  • アプリケーションにはFooが必要です。
  • アプリケーションはコンテナからFooを取得するため、次のようになります。
    • コンテナがBimを作成
    • コンテナはバーを作成し、それにBimを与えます
    • コンテナはFooを作成し、それにバーを与えます
  • アプリケーションがFooを呼び出す
    • フーはバーを呼ぶ
      • バーは何かをします

依存性注入依存性注入コンテナは異なります。

  • 依存性注入は、より良いコードを書くための方法です
  • DIコンテナーは、依存関係の注入に役立つツールです

依存性注入を行うためにコンテナは必要ありません。ただし、コンテナが役立ちます。



55

「依存性注入」とは、パラメーター化されたコンストラクターとパブリックセッターを使用することだけではないのですか。

James Shoreの記事では、比較のために次の例を示しています

依存性注入のないコンストラクタ:

public class Example { 
  private DatabaseThingie myDatabase; 

  public Example() { 
    myDatabase = new DatabaseThingie(); 
  } 

  public void doStuff() { 
    ... 
    myDatabase.getData(); 
    ... 
  } 
} 

依存関係注入を伴うコンストラクター:

public class Example { 
  private DatabaseThingie myDatabase; 

  public Example(DatabaseThingie useThisDatabaseInstead) { 
    myDatabase = useThisDatabaseInstead; 
  }

  public void doStuff() { 
    ... 
    myDatabase.getData(); 
    ... 
  } 
}

確かにDIバージョンでは、引数なしのコンストラクタでmyDatabaseオブジェクトを初期化したくないでしょうか?意味がないようで、オーバーロードされたコンストラクターを呼び出さずにDoStuffを呼び出そうとすると、例外をスローするのに役立ちますか?
Matt Wilko 2013年

new DatabaseThingie()有効なmyDatabaseインスタンスを生成しない場合のみ。
JaneGoodall 2013年

40

依存性注入の概念を理解しやすくするため。電球を切り替える(オン/オフ)ためのスイッチボタンの例を見てみましょう。

依存性注入なし

スイッチは、接続されている電球を事前に知っておく必要があります(ハードコードされた依存関係)。そう、

スイッチ-> PermanentBulb //スイッチは永久電球に直接接続されているため、テストは簡単にはできません

Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}

依存性注入あり

スイッチは、渡された電球のオン/オフを切り替える必要があることだけを認識します。そう、

スイッチ-> Bulb1 OR Bulb2 OR NightBulb(注入された依存関係)

Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}

スイッチと電球のJamesの例の変更:

public class SwitchTest { 
  TestToggleBulb() { 
    MockBulb mockbulb = new MockBulb(); 

    // MockBulb is a subclass of Bulb, so we can 
    // "inject" it here: 
    Switch switch = new Switch(mockBulb); 

    switch.ToggleBulb(); 
    mockBulb.AssertToggleWasCalled(); 
  } 
}

public class Switch { 
  private Bulb myBulb; 

  public Switch() { 
    myBulb = new Bulb(); 
  } 

  public Switch(Bulb useThisBulbInstead) { 
    myBulb = useThisBulbInstead; 
  } 

  public void ToggleBulb() { 
    ... 
    myBulb.Toggle(); 
    ... 
  } 
}`

36

依存性注入(DI)とは何ですか?

他の人が言ったように、Dependency Injection(DI)は、関心のあるクラス(コンシューマークラス)が(UMLの意味で)依存している他のオブジェクトインスタンスの直接的な作成と寿命の管理の責任を取り除きます。これらのインスタンスは代わりに、通常はコンストラクターのパラメーターとして、またはプロパティセッターを介してコンシューマークラスに渡されます(依存関係オブジェクトのインスタンス化とコンシューマークラスへの受け渡しの管理は、通常、制御の反転(IoC)コンテナーによって実行されますが、それは別のトピックです)。 。

DI、DIP、SOLID

具体的には、ロバート・C・マーティンさんのパラダイムで設計オブジェクト指向のSOLID原則DIの可能な実装の1つである依存関係逆転の原則(DIP) DIPがあるDSOLIDマントラ -他のDIP実装はサービスロケータ、およびプラグインのパターンを含みます。

DIPの目的は、クラス間の緊密で具体的な依存関係を切り離すことです。代わりに、抽象化によって結合を緩めることです。これはinterface、使用する言語とアプローチに応じて、abstract classまたはを介して実現できますpure virtual class

DIPがないと、コード(この「消費クラス」と呼んでいます)は具体的な依存関係に直接結び付けられ、この依存関係のインスタンスを取得および管理する方法を知る責任を負うこともあります。

"I need to create/use a Foo and invoke method `GetBar()`"

DIPの適用後、要件は緩和Fooされ、依存関係の存続期間を取得して管理するという懸念は取り除かれました。

"I need to invoke something which offers `GetBar()`"

DIP(およびDI)を使用する理由

この方法でクラス間の依存関係を分離すると、これらの依存関係クラスを、抽象化の前提条件を満たす他の実装で簡単に置き換えることができます(たとえば、同じインターフェイスの別の実装で依存関係を切り替えることができます)。さらに、他の人が述べたように、DIPを介してクラスを分離する最も一般的な理由は、これらの同じ依存関係をスタブまたはモックすることができるため、消費クラスを分離してテストできるようにすることです。

DIの結果の1つは、依存関係オブジェクトが(コンストラクターまたはセッターインジェクションを介して)消費クラスに渡されるようになるため、依存オブジェクトインスタンスの寿命管理が消費クラスによって制御されなくなることです。

これはさまざまな方法で表示できます。

  • 消費クラスによる依存関係のライフスパン制御を保持する必要がある場合は、依存関係クラスインスタンスを作成するための(抽象)ファクトリをコンシューマクラスに挿入することで、制御を再確立できます。コンシューマはCreate、必要に応じて、工場のa を介してインスタンスを取得し、完了したらこれらのインスタンスを破棄できます。
  • または、依存関係インスタンスのライフスパン制御をIoCコンテナーに放棄することもできます(これについては以下で詳しく説明します)。

いつDIを使用するのですか?

  • 依存関係を同等の実装に置き換える必要がある可能性が高い場合、
  • 依存関係を分離してクラスのメソッドを単体テストする必要があるときはいつでも、
  • 依存関係の寿命の不確実性が実験に値する場合(たとえば、MyDepClassスレッドセーフです。シングルトンにして同じインスタンスをすべてのコンシューマに注入するとどうなるでしょうか?)

これが単純なC#の実装です。以下の消費クラスがあるとします。

public class MyLogger
{
   public void LogRecord(string somethingToLog)
   {
      Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
   }
}

一見無害なように見えstaticますが、他の2つのクラスとに2 つの依存関係がSystem.DateTimeありますSystem.Console。これは、ログ出力オプションを制限するだけでなく(誰にも監視されていない場合、コンソールへのログ記録は無意味です)、さらに悪いことに、非決定論的なシステムクロック。

ただしDIP、タイムスタンプの問題を依存関係として抽象化し、MyLogger単純なインターフェースのみに結合することで、このクラスに適用できます。

public interface IClock
{
    DateTime Now { get; }
}

Consoleような抽象化への依存を緩和することもできTextWriterます。Dependency Injectionは通常、constructorインジェクション(依存関係への抽象化をパラメーターとして使用クラスのコンストラクターにSetter Injection渡す)または(定義済みのsetXyz()セッターまたは.Netプロパティを介して依存関係を渡す)として実装されます{set;}。これは、クラスが構築後に正しい状態にあることを保証し、内部依存関係フィールドをreadonly(C#)またはfinal(Java)としてマークできるため、コンストラクター注入が推奨されます。上記の例でコンストラクタインジェクションを使用すると、次のようになります。

public class MyLogger : ILogger // Others will depend on our logger.
{
    private readonly TextWriter _output;
    private readonly IClock _clock;

    // Dependencies are injected through the constructor
    public MyLogger(TextWriter stream, IClock clock)
    {
        _output = stream;
        _clock = clock;
    }

    public void LogRecord(string somethingToLog)
    {
        // We can now use our dependencies through the abstraction 
        // and without knowledge of the lifespans of the dependencies
        _output.Write("{0:yyyy-MM-dd HH:mm:ss} - {1}", _clock.Now, somethingToLog);
    }
}

(具体的にClockは、具体的には、これをに戻すDateTime.Now必要があります。また、2つの依存関係は、コンストラクターインジェクションを介してIoCコンテナーによって提供する必要があります)

自動化された単体テストを構築できます。これにより、依存関係-時間を制御できるようになり、ロガーが正しく動作していることが確実に証明されます。また、出力をスパイすることができます。

[Test]
public void LoggingMustRecordAllInformationAndStampTheTime()
{
    // Arrange
    var mockClock = new Mock<IClock>();
    mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));
    var fakeConsole = new StringWriter();

    // Act
    new MyLogger(fakeConsole, mockClock.Object)
        .LogRecord("Foo");

    // Assert
    Assert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());
}

次のステップ

依存関係の注入は、具体的な依存関係インスタンスを注入(提供)し、寿命インスタンスを管理するために、常にInversion of Controlコンテナー(IoC)に関連付けられています。構成/ブートストラッププロセス中に、IoCコンテナでは以下を定義できます。

  • 各抽象化と構成された具体的な実装の間のマッピング(たとえば、「コンシューマがを要求するときはいつでもIBarConcreteBarインスタンスを返す」
  • 各依存関係のライフスパン管理用にポリシーを設定できます。たとえば、各コンシューマーインスタンスの新しいオブジェクトを作成したり、すべてのコンシューマー間でシングルトン依存関係インスタンスを共有したり、同じ依存関係インスタンスを同じスレッド間でのみ共有したりできます。
  • .Netでは、IoCコンテナはのようなプロトコルを認識しており、構成されたライフスパン管理に沿って依存関係IDisposableの責任を引き受けDisposingます。

通常、IoCコンテナーが構成/ブートストラップされると、バックグラウンドでシームレスに動作し、コーダーは依存関係を気にすることなく手元のコードに集中できます。

DIフレンドリーなコードの鍵は、クラスの静的結合を回避し、依存関係の作成にnew()を使用しないことです

上記の例のように、依存関係の分離にはある程度の設計作業が必要です。開発者にとって、new依存関係を直接使用する習慣を打ち破り、代わりにコンテナーを信頼して依存関係を管理するというパラダイムシフトが必要です。

しかし、特に関心のあるクラスを徹底的にテストする機能には、多くの利点があります。

:生成/マッピング/投影(VIA new ..()POCO / POJO /シリアル化のDTO /エンティティグラフ/匿名JSON突起ら) -すなわち、「データのみ」クラスまたはレコード-使用または方法から返さはされないで(依存性とみなしUMLセンス)、DIの対象ではありません。を使用newしてこれらを投影するだけで問題ありません。


1
問題はDIP!= DIです。DIPは、抽象化を実装から分離することです。A。高レベルモジュールは低レベルモジュールに依存してはなりません。どちらも抽象化に依存する必要があります。B.抽象化は詳細に依存すべきではありません。詳細は抽象化に依存する必要があります。DIは、オブジェクトの作成をオブジェクトの使用から切り離す方法です。
Ricardo Rivaldo、2015年

はい、その違いはボブおじさんのSOLIDパラダイムのパラグラフ2 「DI DIPの可能な実装の1つ」に明確に述べられています。以前の投稿でもこれを明らかにしました。
StuartLC 2017年

25

依存性注入(DI)の要点は、アプリケーションのソースコードをクリーン安定した状態に保つことです。

  • きれいな依存関係の初期化コードの
  • 使用する依存関係に関係なく安定

実際には、すべての設計パターンは、将来の変更が最小限のファイルに影響を与えるように、懸念を分離します。

DIの特定のドメインは、依存関係の構成と初期化の委任です。

例:シェルスクリプトを使用したDI

Java以外で作業することsourceがある場合は、多くのスクリプト言語(Shell、Tclなど、またはimportこの目的で誤用されているPython でも)がどのように頻繁に使用されているかを思い出してください。

簡単なdependent.shスクリプトを考えてみましょう:

#!/bin/sh
# Dependent
touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

スクリプトは依存しています。それ自体でarchive_filesは正常に実行されません(定義されていません)。

実装スクリプトで定義archive_filesarchive_files_zip.shます(zipこの場合に使用):

#!/bin/sh
# Dependency
function archive_files {
    zip files.zip "$@"
}

source依存スクリプトで直接実装スクリプトを実行する代わりに、injector.sh両方の「コンポーネント」をラップする「コンテナ」を使用します。

#!/bin/sh 
# Injector
source ./archive_files_zip.sh
source ./dependent.sh

archive_files 依存関係がちょうどされた注射さ依存するスクリプト。

またはarchive_filesを使用して実装する依存関係を注入した可能性があります。tarxz

例:DIの削除

dependent.shスクリプトが依存関係を直接使用する場合、このアプローチは依存関係ルックアップと呼ばれます依存関係注入とは逆です)。

#!/bin/sh
# Dependent

# dependency look-up
source ./archive_files_zip.sh

touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

ここで問題は、依存する「コンポーネント」が初期化自体を実行する必要があることです。

依存関係の初期化を変更するたびに、「コンポーネント」のソースコードファイルの新しいリリースも必要になるため、「コンポーネント」のソースコードはクリーンで安定でもありません。

最後の言葉

DIは、Javaフレームワークほど大きくは強調されておらず、普及していません。

しかし、それは以下の懸念を分ける一般的なアプローチです。

  • アプリケーション開発単一ソースコードリリースライフサイクル)
  • アプリケーションのデプロイメント(独立したライフサイクルを持つ複数のターゲット環境)

依存関係のルックアップでのみ構成を使用しても、依存関係ごとに構成パラメーターの数が変わる可能性があるため(新しい認証タイプなど)、サポートされる依存関係のタイプの数(新しいデータベースタイプなど)が変わるため、効果がありません。


DIの目的として、依存関係を完了する必要なしに特定のクラス(テスト)を完了する機能を追加します。
デビッド

22

上記の答えはすべて良好です。私の目的は、プログラミングの知識がない人でもコンセプトを理解できるように、コンセプトを簡単に説明することです

依存性注入は、複雑なシステムをより簡単に作成するのに役立つ設計パターンの1つです。

このパターンの私たちの日常生活でのさまざまな応用を見ることができます。例としては、テープレコーダー、VCD、CDドライブなどがあります。

リールツーリールのポータブルテープレコーダー、20世紀半ば。

上の画像は、20世紀半ばのリールツーリールポータブルテープレコーダーの画像です。ソース

テープレコーダーマシンの主な目的は、サウンドを録音または再生することです。

システムを設計する際、サウンドや音楽を録音または再生するにはリールが必要です。このシステムを設計するには2つの可能性があります。

  1. リールをマシン内に配置できます
  2. リールを配置できる場所にフックを提供できます。

最初のものを使用する場合は、マシンを開いてリールを変更する必要があります。リール用のフックを配置する2番目の方法を選択した場合、リールを変更することで音楽を再生できるという追加のメリットがあります。また、リールで何でも再生する機能だけを減らします。

同様に、依存性注入は、依存関係を外部化してコンポーネントの特定の機能のみに焦点を当て、独立したコンポーネントを結合して複雑なシステムを形成できるようにするプロセスです。

依存性注入を使用することで達成された主な利点。

  • 高い凝集力と疎結合。
  • 依存関係を外部化し、責任のみを調べる。
  • 物をコンポーネントとして作成し、組み合わせて高機能の大規模システムを形成する。
  • 独自に開発され、適切にテストされているため、高品質のコンポーネントの開発に役立ちます。
  • 障害が発生した場合、コンポーネントを別のコンポーネントと交換するのに役立ちます。

今日、これらの概念はプログラミングの世界でよく知られているフレームワークの基礎を形成しています。Spring Angularなどは、このコンセプトの上に構築された有名なソフトウェアフレームワークです。

依存性注入は、コンパイル時に他のオブジェクトが依存するオブジェクトのインスタンスを作成するために使用されるパターンです。コンパイル時にどのクラスがその機能を提供するために使用されるか、または単にオブジェクトにプロパティを注入する方法は、依存性注入と呼ばれます。

依存性注入の例

以前は次のようなコードを書いています

Public MyClass{
 DependentClass dependentObject
 /*
  At somewhere in our code we need to instantiate 
  the object with new operator  inorder to use it or perform some method.
  */ 
  dependentObject= new DependentClass();
  dependentObject.someMethod();
}

依存性インジェクションでは、依存性インジェクターがインスタンス化を開始します

Public MyClass{
 /* Dependency injector will instantiate object*/
 DependentClass dependentObject

 /*
  At somewhere in our code we perform some method. 
  The process of  instantiation will be handled by the dependency injector
 */ 

  dependentObject.someMethod();
}

あなたも読むことができます

制御の反転と依存性注入の違い


17

依存性注入とは何ですか?

Dependency Injection(DI)は、相互に依存しているオブジェクトを分離することを意味します。オブジェクトAがオブジェクトBに依存しているとしましょう。そのため、これらのオブジェクトを互いに分離することが考えられます。コンパイル時にもかかわらず、実行時にオブジェクトへの依存関係を共有するのではなく、新しいキーワードを使用してオブジェクトをハードコーディングする必要はありません。話したら

依存性注入が春にどのように機能するか:

新しいキーワードを使用してオブジェクトをハードコードする必要はなく、設定ファイルでBeanの依存関係を定義します。春のコンテナは、すべてをフックする責任があります。

制御の反転(IOC)

IOCは一般的な概念であり、さまざまな方法で表現でき、依存性注入はIOCの具体的な例の1つです。

2種類の依存性注入:

  1. コンストラクターインジェクション
  2. セッター注入

1.コンストラクターベースの依存性注入:

コンストラクターベースのDIは、コンテナーが、それぞれが他のクラスへの依存関係を表す多数の引数を使用してクラスコンストラクターを呼び出すときに実行されます。

public class Triangle {

private String type;

public String getType(){
    return type;
 }

public Triangle(String type){   //constructor injection
    this.type=type;
 }
}
<bean id=triangle" class ="com.test.dependencyInjection.Triangle">
        <constructor-arg value="20"/>
  </bean>

2.セッターベースの依存性注入:

セッターベースのDIは、引数なしのコンストラクターまたは引数なしの静的ファクトリーメソッドを呼び出してBeanをインスタンス化した後、コンテナーがBeanのセッターメソッドを呼び出すことで実現されます。

public class Triangle{

 private String type;

 public String getType(){
    return type;
  }
 public void setType(String type){          //setter injection
    this.type = type;
  }
 }

<!-- setter injection -->
 <bean id="triangle" class="com.test.dependencyInjection.Triangle">
        <property name="type" value="equivialteral"/>

注:必須の依存関係にはコンストラクター引数を使用し、オプションの依存関係にはセッターを使用することをお勧めします。セッターで@Requiredアノテーションに基づくアノテーションを使用する場合は、必須の依存関係としてセッターを作成するために使用できることに注意してください。


15

私が考えることができる最もよい類推は、手術室での外科医とその助手であり、外科医は、外科医が必要なときにさまざまな外科用コンポーネントを提供して、外科医が集中することができるように、さまざまな外科用コンポーネントを提供します彼が最善を尽くすこと(手術)。アシスタントがなければ、外科医はコンポーネントが必要になるたびに自分でコンポーネントを入手する必要があります。

略してDIは、コンポーネントに提供することにより、依存コンポーネントをフェッチするためにコンポーネントに共通する追加の責任(負担)を削除する手法です。

DIは、のように単一責任(SR)の原則に近づけますsurgeon who can concentrate on surgery

DIを使用する場合:ほとんどすべてのプロダクションプロジェクト(小規模/大規模)、特に常に変化するビジネス環境でDIを使用することをお勧めします:)

理由:変更をすばやくテストして市場にプッシュできるように、コードを簡単にテスト可能またはモック可能にする必要があるためです。さらに、あなたがより多くのコントロールを持っているコードベースへの旅であなたをサポートする素晴らしい無料のツール/フレームワークがたくさんあるのになぜあなたはそうしないでしょうか?


@WindRiderありがとう。これ以上は同意できません。人間の生活と人体はデザインの卓越性の素晴らしい例です。脊椎はESBの優れた例です:)...
Anwar Husain

15

例として、2つのクラスClientとがありServiceます。Client使用しますService

public class Service {
    public void doSomeThingInService() {
        // ...
    }
}

依存性注入なし

方法1)

public class Client {
    public void doSomeThingInClient() {
        Service service = new Service();
        service.doSomeThingInService();
    }
}

方法2)

public class Client {
    Service service = new Service();
    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

方法3)

public class Client {
    Service service;
    public Client() {
        service = new Service();
    }
    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

1)2)3)使用

Client client = new Client();
client.doSomeThingInService();

メリット

  • シンプルな

短所

  • テストClientクラスには難しい
  • Serviceコンストラクターを変更するときは、すべての場所でServiceオブジェクトを作成するコードを変更する必要があります

依存性注入を使用する

方法1)コンストラクターの注入

public class Client {
    Service service;

    Client(Service service) {
        this.service = service;
    }

    // Example Client has 2 dependency 
    // Client(Service service, IDatabas database) {
    //    this.service = service;
    //    this.database = database;
    // }

    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

使用する

Client client = new Client(new Service());
// Client client = new Client(new Service(), new SqliteDatabase());
client.doSomeThingInClient();

方法2)セッター注入

public class Client {
    Service service;

    public void setService(Service service) {
        this.service = service;
    }

    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

使用する

Client client = new Client();
client.setService(new Service());
client.doSomeThingInClient();

方法3)インターフェース注入

https://en.wikipedia.org/wiki/Dependency_injectionを確認してください

===

現在、このコードはすでに続きDependency Injection、テストClientクラスの方が簡単です。
ただし、まだnew Service()多くの時間を使用しているため、Serviceコンストラクタを変更するのは適切ではありません。それを防ぐために、
1)のようなDIインジェクターを使用することができますInjector

public class Injector {
    public static Service provideService(){
        return new Service();
    }

    public static IDatabase provideDatatBase(){
        return new SqliteDatabase();
    }
    public static ObjectA provideObjectA(){
        return new ObjectA(provideService(...));
    }
}

使用する

Service service = Injector.provideService();

2)ライブラリを使用する:Android dagger2の場合

メリット

  • テストを簡単にする
  • を変更するときはService、インジェクタークラスで変更するだけです
  • useを使用するとConstructor Injection、のコンストラクターを見るとClientClientクラスの依存関係の数がわかります

短所

  • useを使用するConstructor Injectionと、Service作成時にオブジェクトが作成されますがClientClientクラスの関数を使用せずに使用するServiceことがあるので、作成Serviceは無駄になります

依存性注入の定義

https://en.wikipedia.org/wiki/Dependency_injection

依存関係は、使用できるオブジェクトです(Service
インジェクションは、依存関係(Service)を、Clientそれを使用する依存オブジェクト()に渡すことです


13

つまり、オブジェクトには、ジョブを実行するために必要な数の依存関係のみがあり、依存関係は少ないはずです。さらに、オブジェクトの依存関係は、可能であれば、「具体的な」オブジェクトではなく、インターフェース上にある必要があります。(具体的なオブジェクトとは、キーワードnewで作成されたオブジェクトです。)疎結合は、再利用性と保守性を高め、高価なサービスの代わりに「模擬」オブジェクトを簡単に提供できるようにします。

「依存性注入」(DI)は「制御の反転」(IoC)とも呼ばれ、この疎結合を促進する手法として使用できます。

DIを実装するには、主に2つの方法があります。

  1. コンストラクター注入
  2. セッター注入

コンストラクター注入

これは、オブジェクトの依存関係をコンストラクターに渡す手法です。

コンストラクタは具象オブジェクトではなくインターフェースを受け入れることに注意してください。また、orderDaoパラメータがnullの場合、例外がスローされることに注意してください。これは、有効な依存関係を受け取ることの重要性を強調しています。私の考えでは、コンストラクター注入は、オブジェクトに依存関係を与えるための好ましいメカニズムです。適切な実行のために「Person」オブジェクトにどの依存関係を付与する必要があるかをオブジェクトを呼び出している間、開発者には明らかです。

セッター注入

しかし、次の例を考えてみましょう。依存関係のない10個のメソッドを持つクラスがあり、IDAOに依存する新しいメソッドを追加するとします。コンストラクターインジェクションを使用するようにコンストラクターを変更することもできますが、これにより、あらゆる場所ですべてのコンストラクター呼び出しを変更しなければならない場合があります。または、依存関係を取得する新しいコンストラクターを追加するだけで、開発者は、一方のコンストラクターをもう一方のコンストラクターをいつ使用するかを簡単に知ることができます。最後に、依存関係の作成に非常にコストがかかる場合、めったに使用されない可能性があるのに、なぜ作成してコンストラクターに渡す必要があるのでしょうか。「セッター注入」は、このような状況で使用できる別のDIテクニックです。

セッターインジェクションでは、依存関係がコンストラクターに強制的に渡されることはありません。代わりに、依存関係は、必要なオブジェクトによって公開されるパブリックプロパティに設定されます。以前に暗示されたように、これを行うための主な動機は次のとおりです。

  1. レガシークラスのコンストラクターを変更せずに依存性注入をサポートします。
  2. 高価なリソースまたはサービスをできるだけ遅く、必要なときにのみ作成できるようにする。

上記のコードの例を次に示します。

public class Person {
    public Person() {}

    public IDAO Address {
        set { addressdao = value; }
        get {
            if (addressdao == null)
              throw new MemberAccessException("addressdao" +
                             " has not been initialized");
            return addressdao;
        }
    }

    public Address GetAddress() {
       // ... code that uses the addressdao object
       // to fetch address details from the datasource ...
    }

    // Should not be called directly;
    // use the public property instead
    private IDAO addressdao;

3
私はあなたの最初の段落が質問から離れており、DIの定義ではない(つまり、DIではなくSOLIDを定義しようとしている)と思います。技術的には、100の依存関係がある場合でも、依存関係の注入を使用できます。同様に、具体的な依存関係を注入することも可能です-それでも依存関係の注入です。
Jay Sullivan、

10

誰もがDIのために書いたので、いくつか質問させてください。

  1. クラスに注入されるすべての実際の実装(インターフェースではない)のDIの構成がある場合(たとえば、コントローラーへのサービス)、なぜそれが何らかのハードコーディングではないのですか?
  2. 実行時にオブジェクトを変更したい場合はどうなりますか?たとえば、私の構成では、MyControllerをインスタンス化するときに、FileLoggerをILoggerとして挿入するようにすでに言っています。しかし、DatabaseLoggerを注入したい場合があります。
  3. AClassが必要とするオブジェクトを変更するたびに、クラス自体と構成ファイルという2つの場所を調べる必要があります。それはどのように生活を楽にしますか?
  4. AClassのApropertyが注入されていない場合、モックアウトするのは難しいですか?
  5. 最初の質問に戻ります。new object()の使用が悪い場合、どうしてインターフェースではなく実装を注入するのですか?私たちは実際にインターフェイスを注入していると多くの人が言っていると思いますが、構成により、実行時にではなく、インターフェイスの実装を指定できます。コンパイル時にハードコードされます。

これは、@ Adam Nが投稿した回答に基づいています。

なぜPersonServiceはGroupMembershipServiceについて心配する必要がないのですか?あなたは、GroupMembershipが依存する複数のもの(オブジェクト/プロパティ)を持っていると述べました。PServiceでGMServiceが必要な場合は、プロパティとして使用します。それを注入したかどうかに関係なく、それをあざけることができます。GMServiceに注入したいのは、GMServiceがより具体的な子クラスを持っている場合だけです。子クラスは実行時までわかりません。次に、サブクラスを注入します。または、それをシングルトンまたはプロトタイプとして使用したい場合。正直に言うと、構成ファイルには、コンパイル時に挿入する型(インターフェイス)のサブクラスまで、すべてハードコーディングされています。

編集する

DIでのJose Maria Arranzによる素晴らしいコメント

DIは、依存関係の方向を決定してグルーコードを記述する必要をなくすことにより、まとまりを高めます。

いいえ。依存関係の方向はXML形式または注釈としてあり、依存関係はXMLコードと注釈として記述されます。XMLと注釈はソースコードです。

DIは、すべてのコンポーネントをモジュール化(つまり、交換可能)することで結合を減らし、相互に明確に定義されたインターフェースを備えています。

いいえ。インターフェースに基づいてモジュール式コードを作成するために、DIフレームワークは必要ありません。

置換可能について:非常にシンプルな.propertiesアーカイブとClass.forNameを使用して、変更できるクラスを定義できます。コードの任意のクラスを変更できる場合、Javaは適切ではありません。スクリプト言語を使用してください。ちなみに、アノテーションは再コンパイルしないと変更できません。

私の意見では、DIフレームワークの唯一の理由は、ボイラープレートの削減です。よくできたファクトリシステムを使用すると、好みのDIフレームワークと同じ、より制御された、より予測可能なものを実行できます。DIフレームワークはコードの削減を約束します(XMLと注釈もソースコードです)。問題は、このボイラープレートの削減が非常に単純なケース(クラスごとに1つのインスタンスなど)で実際に行われることであり、現実の世界では、適切なサービスオブジェクトを選択することが、クラスをシングルトンオブジェクトにマッピングするほど簡単ではない場合があります。


8

人気のある回答は役に立たない方法で依存性注入を定義しているため、役に立ちません。「依存性」とは、オブジェクトXが必要とする既存の他のオブジェクトを意味することに同意しましょう。しかし、「依存性注入」を行っているとは言いません。

$foo = Foo->new($bar);

コンストラクタにパラメータを渡すことを呼び出すだけです。コンストラクターが発明されて以来、これを定期的に行ってきました。

「依存性注入」は「制御の反転」の一種と見なされます。つまり、呼び出し元から一部のロジックが取り出されます。これは、呼び出し元がパラメーターを渡す場合には当てはまりません。そのため、それがDIである場合、DIは制御の反転を意味しません。

DIは、呼び出し元と依存関係を管理するコンストラクターの間に中間レベルがあることを意味します。Makefileは、依存性注入の簡単な例です。「呼び出し元」はコマンドラインで「make bar」と入力する人であり、「コンストラクタ」はコンパイラです。Makefileは、barがfooに依存することを指定します。

gcc -c foo.cpp; gcc -c bar.cpp

する前に

gcc foo.o bar.o -o bar

「make bar」と入力する人は、barがfooに依存していることを知る必要はありません。「make bar」とgccの間に依存関係が挿入されました。

中間レベルの主な目的は、依存関係をコンストラクターに渡すだけでなく、すべての依存関係を1か所にリストすることですそれらをコーダーから非表示にすることです(コーダーに提供させるためではありません)。

通常、中間レベルは、構築されたオブジェクトのファクトリを提供します。ファクトリは、要求された各オブジェクトタイプが満たす必要があるロールを提供する必要があります。これは、構造の詳細を隠す中間レベルを設定することにより、工場によって課される抽象化のペナルティがすでに発生しているため、工場を使用することもできます。


8

依存性注入とは、コードの一部(例:クラス)が依存関係(コードの他の部分、たとえば、他のクラス、依存するもの)にモジュール化された方法でアクセスできるようにする(実際にはとにかく)方法を意味します。必要に応じて、自由に変更またはオーバーライドしたり、別のときにロードすることもできます)

(そしてps、はい、それはかなり単純な概念のために過度に宣伝された25 $の名前になりました)、私の.25セント


8

私はすでに多くの答えがあることを知っていますが、これは非常に役に立ちました:http : //tutorials.jenkov.com/dependency-injection/index.html

依存なし:

public class MyDao {

  protected DataSource dataSource = new DataSourceImpl(
    "driver", "url", "user", "password");

  //data access methods...
  public Person readPerson(int primaryKey) {...}     
}

依存:

public class MyDao {

  protected DataSource dataSource = null;

  public MyDao(String driver, String url, String user, String password) {
    this.dataSource = new DataSourceImpl(driver, url, user, password);
  }

  //data access methods...
  public Person readPerson(int primaryKey) {...}
}

DataSourceImplインスタンス化がコンストラクターに移動されていることに注意してください。コンストラクタは、が必要とする4つの値である4つのパラメータを取りますDataSourceImpl。けれどもMyDaoこのクラスは、まだこれらの四つの値、それはもはや満足し、これらの依存関係自体に依存します。これらは、MyDaoインスタンスを作成するクラスによって提供されます。


1
DataSourceImpがすでに構築されているインターフェースを介してDIを渡さないのではないですか?
PmanAce 2016年

6

依存関係の注入は、一般に「依存関係の難読化」要件と呼ばれる可能性のあるものに対する1つの可能なソリューションです。依存関係の難読化は、依存関係を必要とするクラスに依存関係を提供するプロセスから「明白な」性質を取り除き、何らかの方法で、その依存関係をそのクラスに提供することを難読化する方法です。これは必ずしも悪いことではありません。実際、依存関係がクラスに提供される方法を難読化することにより、クラス外の何かが依存関係の作成を担当します。つまり、さまざまなシナリオで、変更を行わずに依存関係の異なる実装をクラスに提供できます。クラスに。これは、プロダクションモードとテストモードの切り替え(たとえば、「模擬」サービスの依存関係の使用)に最適です。

残念なことに、一部の人々は、依存関係の難読化を行うには特殊なフレームワークが必要であると想定しているため、特定のフレームワークを使用しないことを選択した場合、どういうわけか「より少ない」プログラマーになります。多くの人が信じているもう1つの非常に不安な神話は、依存性注入が依存性難読化を実現する唯一の方法であるということです。これは明白かつ歴史的に明らかに100%間違っていますが、依存関係の難読化の要件には依存関係の注入に代わるものがあることを一部の人々に納得させるのに苦労します。

プログラマーは何年にもわたって依存関係の難読化の要件を理解しており、依存関係の注入が考案される前と後の両方で、多くの代替ソリューションが進化してきました。ファクトリパターンがありますが、特定のインスタンスへの注入が不要なThreadLocalを使用する多くのオプションもあります。依存関係は、便利な静的ゲッターメソッドを介してオブジェクトを利用できるようにするという利点があるスレッドに効果的に注入されます。ます。それを必要とするクラスに注釈を追加し、それを実現するために複雑なXML「接着剤」を設定する必要なしに、それを必要とするクラス。永続性(JPA / JDOなど)に依存関係が必要な場合、「透過的な永続性」を簡単に実現でき、POJOだけで構成されたドメインモデルとビジネスモデルクラスを使用できます(つまり、フレームワーク固有ではなく、アノテーションにロックされません)。



5

技術的な説明に進む前に、最初に実際の例を使って視覚化します。依存関係の注入を学ぶための技術的な知識はたくさんありますが、私のような人々はそのコアコンセプトを理解できない最長時間です。

最初の画像では、たくさんの団結を持つ自動車工場があると仮定します。車は実際には組み立てユニットに組み込まれていますが、エンジンシートホイールが必要です。したがって、アセンブリユニットはこれらすべてのユニットに依存し、それらは依存関係です。工場のです。

現在、この工場ですべてのタスクを維持するのは複雑すぎると感じます。メインタスク(アセンブリユニットでの車の組み立て)とともに、他のユニットにも集中する必要があるためです。です。現在、維持するのに非常に費用がかかり、工場の建物は巨大であるため、家賃に余分な費用がかかります。

次に、2番目の画像を見てください。ホイールシートエンジンを自己生産コストよりも安く提供するプロバイダー企業を見つけたら、今では工場でそれらを作る必要はありません。あなたは今あなたの組立ユニットのためだけに小さな建物を借りることができますので、メンテナンス作業が軽減され、余分なレンタルコストを削減できます。これで、メインタスク(車の組み立て)のみに集中することもできます。

これで、自動車を組み立てるためのすべての依存関係プロバイダーから工場に注入されたと言えます。これは実際の依存性注入(DI)の例ですです。

技術用語では、依存性注入とは、あるオブジェクト(または静的メソッド)が別のオブジェクトの依存関係を提供する手法です。したがって、オブジェクトを作成するタスクを他の人に転送し、依存関係を直接使用することを、依存関係注入と呼びます。

これは、専門用語でDIを学ぶのに役立ちます。これにより、DIを使用する場合と使用しない場合が示されます。

1つの自動車工場のすべて

シンプルな自動車工場


1
40イッシュのうち最も明確な答え。実際の例と画像。+1。受け入れられる答えでなければなりません。
マルケレミ

4

Book Apress.Spring.Persistence.with.Hibernate.Oct.2010より

依存関係の挿入の目的は、アプリケーションのビジネスロジックから外部ソフトウェアコンポーネントを解決する作業を切り離すことです。依存関係の挿入がないと、コンポーネントが必要なサービスにアクセスする方法の詳細が、コンポーネントのコードと混同される可能性があります。これは、エラーの可能性を増大させるだけでなく、コードの肥大化を追加し、メンテナンスの複雑さを増大させます。コンポーネントをより密接に結合し、リファクタリングまたはテスト時に依存関係を変更することを困難にします。


4

Dependency Injection(DI)は、OOPの基本機能である、あるオブジェクトと別のオブジェクトの関係を使用するデザインパターンの1つです。継承は1つのオブジェクトを継承してより複雑で特定の別のオブジェクトを実行しますが、関係または関連付けは、属性を使用して1つのオブジェクトから別のオブジェクトへのポインタを作成するだけです。DIの機能は、インターフェイスや隠しコードと同様に、OOPの他の機能と組み合わされています。図書館に顧客(購読者)がいて、簡単にするために1冊しか借りられないとします。

本のインターフェース:

package com.deepam.hidden;

public interface BookInterface {

public BookInterface setHeight(int height);
public BookInterface setPages(int pages);   
public int getHeight();
public int getPages();  

public String toString();
}

次に、さまざまな種類の本を用意できます。タイプの1つはフィクションです。

package com.deepam.hidden;

public class FictionBook implements BookInterface {
int height = 0; // height in cm
int pages = 0; // number of pages

/** constructor */
public FictionBook() {
    // TODO Auto-generated constructor stub
}

@Override
public FictionBook setHeight(int height) {
  this.height = height;
  return this;
}

@Override
public FictionBook setPages(int pages) {
  this.pages = pages;
  return this;      
}

@Override
public int getHeight() {
    // TODO Auto-generated method stub
    return height;
}

@Override
public int getPages() {
    // TODO Auto-generated method stub
    return pages;
}

@Override
public String toString(){
    return ("height: " + height + ", " + "pages: " + pages);
}
}

これで、購読者は本に関連付けることができます。

package com.deepam.hidden;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Subscriber {
BookInterface book;

/** constructor*/
public Subscriber() {
    // TODO Auto-generated constructor stub
}

// injection I
public void setBook(BookInterface book) {
    this.book = book;
}

// injection II
public BookInterface setBook(String bookName) {
    try {
        Class<?> cl = Class.forName(bookName);
        Constructor<?> constructor = cl.getConstructor(); // use it for parameters in constructor
        BookInterface book = (BookInterface) constructor.newInstance();
        //book = (BookInterface) Class.forName(bookName).newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    return book;
}

public BookInterface getBook() {
  return book;
}

public static void main(String[] args) {

}

}

3つのクラスはすべて、それ自体の実装のために非表示にすることができます。これで、このコードをDIに使用できます。

package com.deepam.implement;

import com.deepam.hidden.Subscriber;
import com.deepam.hidden.FictionBook;

public class CallHiddenImplBook {

public CallHiddenImplBook() {
    // TODO Auto-generated constructor stub
}

public void doIt() {
    Subscriber ab = new Subscriber();

    // injection I
    FictionBook bookI = new FictionBook();
    bookI.setHeight(30); // cm
    bookI.setPages(250);
    ab.setBook(bookI); // inject
    System.out.println("injection I " + ab.getBook().toString());

    // injection II
    FictionBook bookII = ((FictionBook) ab.setBook("com.deepam.hidden.FictionBook")).setHeight(5).setPages(108); // inject and set
    System.out.println("injection II " + ab.getBook().toString());      
}

public static void main(String[] args) {
    CallHiddenImplBook kh = new CallHiddenImplBook();
    kh.doIt();
}
}

依存性注入を使用する方法はたくさんあります。シングルトンなどと組み合わせることも可能ですが、基本的には別のオブジェクト内にオブジェクト型の属性を作成することで実現される関連付けのみです。有用性は、機能の点でのみであり、何度も記述する必要があるコードは、常に前もって準備され、実行されます。これが、DIがInversion of Control(IoC)と緊密に結びついている理由です。つまり、プログラムは別の実行中のモジュールに制御を渡し、コードへのBeanの注入を行います。(注入可能な各オブジェクトは、署名またはBeanと見なすことができます。)たとえば、Springでは、作成と初期化によって行われます。 ApplicationContextをます。これは私たちのために機能しますコンテナ。コードで単純にコンテキストを作成し、Beanの初期化を呼び出します。その瞬間、注入は自動的に行われました。


4

5歳児の依存症注射。

冷蔵庫から出て自分で物を取り出すと、問題が発生する可能性があります。ドアを開けたままにしておくと、ママやパパが欲しくないものを手に入れるかもしれません。私たちが持っていないものや期限切れのものを探しているかもしれません。

あなたがしなければならないことは、「昼食と一緒に飲むものが必要です」という必要性を述べることです。それから、あなたが座って食べるときに何かがあることを確認します。


1
これは明らかに親の答えです。;)
マルシェレミ

4

Christoffer Noring著、Pablo Deelemanの本「Learning Angular-Second Edition」:

アプリケーションが成長し進化するにつれて、コードエンティティのそれぞれが内部で他のオブジェクトのインスタンスを必要とします。これは、ソフトウェアエンジニアリングの世界では依存関係としてよく知られています。このような依存関係を依存型クライアントに渡すアクションは、インジェクションと呼ばれます。また、別のコードエンティティ(インジェクター)の参加も伴います。インジェクターインスタンス化ブートストラップを担当します、必要な依存関係のそのため、クライアントに正常に挿入されたその瞬間から使用できるようになります。クライアントは自身の依存関係インスタンス化する方法について何も知らず、インターフェースのみを認識しているため、これは非常に重要ですそれらを使用するために実装です。」

投稿者:アントンモイセエフ。本「Angular Development with Typescript、Second Edition」:

「要するに、DIは、あなたがコードを書くことができます疎結合な方法と、あなたなりのコードがより多くのテスト可能再利用可能。」


3

簡単に言えば、依存性注入(DI)は、異なるオブジェクト間の依存性または密結合を削除する方法です。依存性注入は、各オブジェクトにまとまりのある動作を与えます。

DIは、SpringのIOCプリンシパルの実装であり、「私たちに電話しないでください。依存性注入プログラマを使用すると、新しいキーワードを使用してオブジェクトを作成する必要はありません。

オブジェクトは、Springコンテナーに一度読み込まれ、getBean(String beanName)メソッドを使用してSpringコンテナーからオブジェクトをフェッチすることで、必要なときにいつでも再利用できます。


3

依存関係の注入は、Spring Frameworkに関連する概念の中心です。任意のプロジェクトの春のフレームワークを作成することは、重要な役割を果たす可能性があります。ここで、依存関係の注入はピッチャーで行われます。

実際、JavaでクラスAとクラスBの2つの異なるクラスを作成し、クラスAで使用したいクラスBで使用できる関数があるとすると、その時点で依存性注入を使用できます。あるクラスのオブジェクトを別のクラスに作成できるのと同じように、クラス全体を別のクラスに注入してアクセス可能にします。これにより、依存関係を克服できます。

依存性注入は、2つのクラスを単純に接着し、同時に、それらを別々に保持します。

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