依存関係の逆転の原理とは何ですか?なぜそれが重要なのですか?


178

依存関係の逆転の原理とは何ですか?なぜそれが重要なのですか?



3
ウィキペディアからの「高レベル」および「低レベル」の用語を使用して、ここで途方もない量の回答。これらの用語にはアクセスできず、多くの読者をこのページに導きます。ウィキペディアを逆流する場合は、これらの用語を回答定義してコンテキストを提供してください
8bitjunkie

回答:


107

このドキュメントをチェックしてください:依存関係の逆転の原則

それは基本的に言う:

  • 高レベルのモジュールは、低レベルのモジュールに依存するべきではありません。どちらも抽象化に依存する必要があります。
  • 抽象化は詳細に依存するべきではありません。詳細は抽象化に依存する必要があります。

なぜそれが重要なのか、つまり、変更は危険であり、実装ではなく概念に依存することで、呼び出しサイトでの変更の必要性を減らします。

効果的に、DIPは異なるコード部分間の結合を減らします。たとえば、ロギングファシリティを実装する方法はたくさんありますが、使用方法は比較的安定しているはずです。ロギングの概念を表すインターフェースを抽出できれば、このインターフェースはその実装よりも時間的にはるかに安定しているはずであり、そのロギングメカニズムを維持または拡張している間に加えた変更によるコールサイトへの影響ははるかに少ないはずです。

また、実装をインターフェイスに依存させることで、特定の環境により適した実装を実行時に選択できるようになります。場合によっては、これも興味深いかもしれません。


31
この答えは、なぜDIPが重要であるのか、あるいはDIPが何であるのかについては述べていません。私は公式のDIPドキュメントを読みましたが、これは、高レベルのモジュールは再利用可能であるという欠陥のある仮定に基づいているため、これは実際には不十分で正当化されていない「原則」だと思います。
–Rogério

3
一部のオブジェクトのディペンデンシーグラフを考えてみます。オブジェクトにDIPを適用します。これで、どのオブジェクトも他のオブジェクトの実装に依存しなくなります。単体テストが簡単になりました。後で再利用するためのリファクタリングが可能です。設計変更の変更範囲は非常に限られています。設計の問題はカスケードしません。データ依存関係の反転については、AIパターン「Blackboard」も参照してください。一緒に、ソフトウェアを理解しやすく、保守可能で信頼できるようにする非常に強力なツール。このコンテキストでは依存性注入を無視します。それは無関係です。
ティムウィリスクロフト2009

6
クラスBを使用するクラスAは、AがBの実装に「依存する」という意味ではありません。Bのパブリックインターフェイスのみに依存します。AとBの両方が現在依存している別個の抽象化を追加することは、AがBのパブリックインターフェイスにコンパイル時に依存しなくなることを意味します。ユニット間の分離は、これらの追加の抽象化なしで簡単に実現できます。Javaと.NETには、すべての状況(静的メソッド、コンストラクターなど)を処理する特定のモッキングツールがあります。DIPを適用すると、ソフトウェアが複雑になり、保守性が低下し、テストが困難になる傾向があります。
–Rogério

1
では、制御の逆転とは何でしょうか。依存関係の逆転を実現する方法?
user20358 2012

2
@Rogerio、以下のDerek Greerの応答を参照してください、彼はそれを説明します。AFAIK、DIPは、Aが必要とするものを要求するのはAであり、Aが必要とするものを言うのはBではないと言っています。AニーズがBで与えられるが、A.用すべきではないように、インターフェース
ejaenv

145

『アジャイルソフトウェア開発、原則、パターン、および実践』およびC#のアジャイル原則、パターン、および実践に関する書籍は、依存関係逆転の原則の背後にある元の目標と動機を完全に理解するための最良のリソースです。記事「依存関係の逆転の原則」も優れたリソースですが、それが最終的に前述の本に取り入れられたドラフトの要約版であることから、それは概念のいくつかの重要な議論を省いていますこの原則を、「Design Patterns(Gamma、et al。)」の本にある「実装ではなくインターフェイスへのプログラム」へのより一般的なアドバイスから区別するための鍵となるパッケージとインターフェイスの所有権。

要約すると、依存関係の逆転の原則は、主に依存関係の従来の方向を「上位レベル」コンポーネントから「下位レベル」コンポーネントに逆転させ、「下位レベル」コンポーネントが「上位レベル」コンポーネントが所有するインターフェースに依存するようにすることです。 。(注:ここでの「より高いレベル」のコンポーネントは、外部の依存関係/サービスを必要とするコンポーネントを指します。必ずしもレイヤードアーキテクチャ内での概念的な位置ではありません。)その際、理論的にはコンポーネントからシフトしているため、カップリングはそれほど減少していません。理論的にはより価値のあるコンポーネントには価値がありません。

これは、コンポーネントのコンシューマーが実装を提供する必要のあるインターフェースに関して外部依存関係が表現されるコンポーネントを設計することによって実現されます。言い換えると、定義されたインターフェースは、コンポーネントの使用方法ではなく、コンポーネントに必要なものを表します(たとえば、「IDoSomething」ではなく「INeedSomething」)。

依存関係の逆転の原則が言及していないのは、インターフェイスを使用して依存関係を抽象化する簡単な方法です(たとえば、MyService→[ILogger⇐Logger])。これにより、依存関係の特定の実装の詳細からコンポーネントが分離されますが、コンシューマと依存関係の関係は反転しません(例:[MyService→IMyServiceLogger]⇐Logger。

依存関係の逆転の原則の重要性は、機能の一部(ロギング、検証など)の外部依存関係に依存するソフトウェアコンポーネントを再利用できるという単一の目標にまで蒸留できます。

再利用というこの一般的な目標の範囲内で、2つのサブタイプの再利用を描くことができます。

  1. サブ依存関係の実装を持つ複数のアプリケーション内でソフトウェアコンポーネントを使用する(例:DIコンテナーを開発していて、ロギングを提供したいが、コンテナーを使用するすべてのユーザーが必要になるように、コンテナーを特定のロガーに結合したくない場合選択したロギングライブラリを使用します)。

  2. 進化するコンテキスト内でソフトウェアコンポーネントを使用する(たとえば、実装の詳細が進化するアプリケーションの複数のバージョン間で同じままであるビジネスロジックコンポーネントを開発した)。

インフラストラクチャライブラリなど、複数のアプリケーション間でコンポーネントを再利用する最初のケースでは、そのような依存関係への依存関係を取得する必要があるため、コンシューマを自分のライブラリのサブ依存関係に結合することなく、コアインフラストラクチャのニーズをコンシューマに提供することが目標です。消費者も同じ依存関係を必要とします。これは、ライブラリのコンシューマーが同じインフラストラクチャのニーズ(NLogとlog4netなど)に異なるライブラリを使用することを選択した場合、またはバージョンとの下位互換性がない必要なライブラリの新しいバージョンを使用することを選択した場合に問題になる可能性があります。ライブラリで必要です。

ビジネスロジックコンポーネント(つまり「上位レベルのコンポーネント」)を再利用する2番目のケースの目的は、アプリケーションのコアドメイン実装を、実装の詳細の変化するニーズ(つまり、永続ライブラリ、メッセージングライブラリの変更/アップグレード)から分離することです。 、暗号化戦略など)。理想的には、アプリケーションの実装の詳細を変更しても、アプリケーションのビジネスロジックをカプセル化しているコンポーネントが壊れないようにする必要があります。

注:一部の人は、この2番目のケースを実際の再利用として説明することに反対する場合があります。単一の進化するアプリケーション内で使用されるビジネスロジックコンポーネントなどのコンポーネントは、単一の使用のみを表すためです。ただし、ここでの考え方は、アプリケーションの実装の詳細を変更するたびに新しいコンテキストが提供されるため、ユースケースが異なるということですが、最終的な目標は、分離か移植性かで区別できます。

この2番目のケースで依存関係の逆転の原則に従うといくつかの利点が得られますが、JavaやC#などの現代の言語に適用される場合、その値は大幅に低下し、おそらく無関係になります。前に説明したように、DIPには、実装の詳細を個別のパッケージに完全に分離することが含まれます。ただし、進化するアプリケーションの場合、ビジネスドメインで定義されたインターフェースを単に利用することで、実装の詳細が最終的に同じパッケージ内にある場合でも、実装の詳細コンポーネントのニーズの変化により、上位レベルのコンポーネントを変更する必要がなくなります。 。原則のこの部分は、原則が成文化されたときに見た言語に関連していた側面(つまり、C ++)を反映していますが、新しい言語には関係ありません。それは言った、

インターフェースの単純な使用、依存性注入、および分離されたインターフェースパターンに関連するため、この原則についての詳細な説明は、ここで見つけることができます。さらに、JavaScriptなどの動的型付け言語に原則がどのように関連するかについての議論は、ここにあります


17
ありがとう。私の答えがいかに要点を逃しているかがわかります。間の差たMyService→[ILoggerです⇐ロガー][MyServiceで→IMyServiceLogger]⇐Loggerは微妙ですが重要です。
Patrick McElhaney、2009

2
同じ行ではその非常によくここで説明:lostechies.com/derickbailey/2011/09/22/...
ejaenvを

1
依存関係注入の標準的なユースケースとして、ロガーの使用をやめてほしいと思いました。特にほぼ反パターンのlog4netに関連して。それはさておき、気まぐれな説明!
Casper Leon Nielsen

4
@ Casper Leon Nielsen-DIPはDIとは何の関係もありません。同義語でも、同等の概念でもありません。
TSmith 2013

4
@ VF1概要の段落で述べたように、依存関係逆転原理の重要性は主に再利用です。Johnがロギングライブラリへの外部依存関係を持つライブラリをリリースし、SamがJohnのライブラリを使用したい場合、Samは一時的なロギング依存関係を引き受けます。サムは、ジョンが選択したログライブラリがないと、アプリケーションをデプロイできません。ただし、ジョンがDIPに従う場合、サムはアダプタを自由に提供し、彼が選択したロギングライブラリを使用できます。DIPは利便性ではなく、結合です。
Derek Greer

14

ソフトウェアアプリケーションを設計するとき、低レベルのクラスを基本および基本操作(ディスクアクセス、ネットワークプロトコルなど)を実装するクラス、高レベルのクラスを複雑なロジック(ビジネスフローなど)をカプセル化するクラスと見なすことができます。

最後のものは低レベルのクラスに依存しています。このような構造を実装する自然な方法は、低レベルのクラスを記述し、それらを用意したら、複雑な高レベルのクラスを記述します。高レベルのクラスは他のクラスで定義されているため、これは論理的な方法のようです。しかし、これは柔軟な設計ではありません。低レベルのクラスを置き換える必要がある場合はどうなりますか?

依存関係逆転の原則は次のように述べています:

  • 高レベルのモジュールは、低レベルのモジュールに依存するべきではありません。どちらも抽象化に依存する必要があります。
  • 抽象化は詳細に依存すべきではありません。詳細は抽象化に依存する必要があります。

この原則は、ソフトウェアの高レベルのモジュールが低レベルのモジュールに依存する必要があるという従来の概念を「逆転」しようとしています。ここで、高レベルのモジュールは、低レベルのモジュールによって実装される抽象化(たとえば、インターフェースのメソッドの決定)を所有します。したがって、下位レベルのモジュールを上位レベルのモジュールに依存させます。


11

適切に適用された依存関係の反転は、アプリケーションのアーキテクチャ全体のレベルで柔軟性と安定性を提供します。これにより、アプリケーションをより安全かつ安定して進化させることができます。

従来のレイヤードアーキテクチャ

従来、レイヤードアーキテクチャのUIはビジネスレイヤーに依存しており、これはデータアクセスレイヤーに依存していました。

レイヤー、パッケージ、またはライブラリを理解する必要があります。コードがどうなるか見てみましょう。

データアクセスレイヤー用のライブラリまたはパッケージがあります。

// DataAccessLayer.dll
public class ProductDAO {

}

また、データアクセスレイヤーに依存する別のライブラリまたはパッケージレイヤーのビジネスロジック。

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

依存関係の逆転を伴う階層化アーキテクチャ

依存関係の逆転は、次のことを示しています。

高レベルのモジュールは、低レベルのモジュールに依存するべきではありません。どちらも抽象化に依存する必要があります。

抽象化は詳細に依存すべきではありません。詳細は抽象化に依存する必要があります。

高レベルモジュールと低レベルは何ですか?ライブラリやパッケージなどのモジュールを考えると、高レベルモジュールは、従来依存関係があり、依存する低レベルのモジュールになります。

つまり、モジュールの高レベルはアクションが呼び出される場所であり、低レベルはアクションが実行される場所です。

この原則から引き出す合理的な結論は、具体化の間に依存関係があるべきではないということですが、抽象化への依存関係がなければなりません。しかし、私たちが取っているアプローチによると、依存性に依存する投資を誤って適用することができますが、抽象化です。

コードを次のように変更するとします。

抽象化を定義するデータアクセスレイヤー用のライブラリまたはパッケージがあります。

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

また、データアクセスレイヤーに依存する別のライブラリまたはパッケージレイヤーのビジネスロジック。

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

ビジネスとデータアクセス間の抽象化の依存関係に依存していますが、同じままです。

依存関係を逆にするには、永続性インターフェースを、この高レベルのロジックまたはドメインが存在するモジュールまたはパッケージで定義し、低レベルモジュールでは定義しないようにする必要があります。

最初にドメインレイヤーとは何かを定義し、その通信の抽象化は永続性を定義します。

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

永続化レイヤーがドメインに依存した後、依存関係が定義されている場合はここで反転します。

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(ソース:xurxodev.com

原則を深める

コンセプトをよく理解し、目的と利点を深めることが重要です。機械的にとどまり、典型的なケースリポジトリを学習する場合、依存関係の原則を適用できる場所を特定できません。

しかし、なぜ依存関係を反転させるのでしょうか?特定の例を超える主な目的は何ですか?

これにより、一般に、安定性の低いものに依存しない最も安定したものをより頻繁に変更できます。

永続性と通信するように設計されたドメインロジックまたはアクションよりも、データベースまたはテクノロジが同じデータベースにアクセスするための永続性タイプを変更する方が簡単です。このため、この変更が発生した場合に永続性を変更する方が簡単なので、依存関係は逆になります。この方法では、ドメインを変更する必要はありません。ドメイン層はすべての中で最も安定しているため、何にも依存してはなりません。

しかし、このリポジトリの例だけではありません。この原則が適用される多くのシナリオがあり、この原則に基づくアーキテクチャがあります。

アーキテクチャー

依存関係の逆転がその定義の鍵となるアーキテクチャがあります。すべてのドメインで最も重要であり、ドメインと残りのパッケージまたはライブラリとの間の通信プロトコルが定義されていることを示すのは抽象化です。

クリーンなアーキテクチャ

クリーンなアーキテクチャドメインは、中心部に位置し、依存関係を示す矢印の方向に見れば、最も重要な、安定した層が何であるか明らかです。外層は不安定なツールと見なされるため、それらに依存することは避けてください。


(ソース:8thlight.com

六角形のアーキテクチャ

これは、六角形のアーキテクチャでも同じように発生します。ドメインも中央部分にあり、ポートはドミノから外部への通信の抽象化です。ここでも、ドメインが最も安定しており、従来の依存関係が逆転していることが明らかです。


それが何を言っているのかはわかりませんが、この文は一貫性がありません。おそらく修正したいですか?
マークアメリー

9

私にとって、Dependency Inversion Principleは、公式記事で説明されているように、本質的に再利用可能性が低いモジュールの再利用性を高めるための見当違いの試みであり、C ++言語の問題を回避する方法でもあります。

C ++の問題は、ヘッダーファイルには通常、プライベートフィールドとメソッドの宣言が含まれていることです。したがって、高レベルC ++モジュールに低レベルモジュールのヘッダーファイルが含まれている場合、そのモジュールの実際の実装の詳細に依存します。そして、それは明らかに、良いことではありません。しかし、これは今日一般的に使用されているより現代的な言語では問題ではありません。

前者は通常後者よりもアプリケーション/コンテキスト固有であるため、高レベルモジュールは本質的に低レベルモジュールよりも再利用性が低くなります。たとえば、UI画面を実装するコンポーネントは最高レベルであり、アプリケーションに非常に(完全に)固有です。このようなコンポーネントを別のアプリケーションで再利用しようとすると、生産性が低下し、過剰なエンジニアリングにつながるだけです。

したがって、コンポーネントBに依存する(コンポーネントAに依存しない)コンポーネントAの同じレベルでの別個の抽象化の作成は、コンポーネントAが異なるアプリケーションまたはコンテキストでの再利用に本当に役立つ場合にのみ実行できます。そうでない場合、DIPを適用するのは悪い設計です。


高レベルの抽象化がそのアプリケーションのコンテキストでのみ有用である場合でも、実際の実装をテスト用のスタブに置き換える(または通常Webで利用可能なアプリケーションへのコマンドラインインターフェイスを提供する)ときに価値があります
Przemekポクリフカ

3
DIPは言語間で重要であり、C ++自体とは関係ありません。高レベルのコードがアプリケーションを離れることがない場合でも、DIPを使用すると、低レベルのコードを追加または変更するときにコードの変更を含めることができます。これにより、メンテナンスコストと意図しない変更の影響の両方が削減されます。DIPはより高いレベルの概念です。理解できない場合は、さらにグーグルする必要があります。
Dirk Bester 2013

3
私はC ++について私が言ったことを誤解していると思います。それはDIPの動機にすぎませんでした。間違いなくそれよりも一般的です。DIPに関する公式の記事では、低レベルのモジュールの変更の影響を受けないようにすることで、高レベルのモジュールの再利用をサポートすることが主な動機であることを明確にしています。再利用する必要がないので、やりすぎややりすぎになる可能性が非常に高くなります。(読みましたか?C ++の問題についても説明しています。)
Rogério2013

1
DIPの逆の適用を見落としていませんか?つまり、上位レベルのモジュールは基本的にアプリケーションを実装します。あなたの主な関心事はそれを再利用することではなく、それを下位レベルのモジュールの実装にあまり依存しないようにすることであり、将来の変更に対応するために更新することで、アプリケーションを時間や新しいテクノロジーの破壊から切り離します。これは、WindowsOSの置き換えを容易にするためではなく、WindowsOSをFAT / HDDの実装の詳細にあまり依存しないようにするためです。これにより、新しいNTFS / SSDをWindowsOSにプラグインして影響を与えることはできません。
knockNrod

1
UI画面は、絶対に最高レベルのモジュールではないか、アプリケーション用ではありません。最高レベルのモジュールは、ビジネスルールを含むモジュールです。最高レベルのモジュールは、再利用の可能性によっても定義されていません。この記事のおじさんボブの簡単な説明を確認してください:blog.cleancoder.com/uncle-bob/2016/01/04/...
フンババ

8

基本的にそれは言う:

クラスは、具体的な詳細(実装)ではなく、抽象化(インターフェース、抽象クラスなど)に依存する必要があります。


そんなに簡単なことでしょうか?DerekGreerが言及したように、長い記事や本さえありますか?私は実際には単純な答えを探していましたが、それがその単純なものである場合は信じられない
でしょう

1
@ darius-v「抽象化を使用する」という別のバージョンではありません。それは誰がインターフェースを所有するかについてです。プリンシパルは、クライアント(高レベルのコンポーネント)がインターフェイスを定義し、低レベルのコンポーネントがそれらを実装する必要があると述べています。
ボラン

6

良い答えと良い例は、すでにここで他の人によって与えられています。

その理由のDIPは重要であり、それは「緩やかにデザインを結合された」OO-原則を保証するからです。

ソフトウェア内のオブジェクトは、低レベルのオブジェクトに依存して、一部のオブジェクトがトップレベルのオブジェクトである階層に入らないようにしてください。低レベルのオブジェクトの変更は、トップレベルのオブジェクトに波及し、ソフトウェアの変更が非常に脆弱になります。

「トップレベル」のオブジェクトが非常に安定していて、変更に対して脆弱ではないようにしたいので、依存関係を逆にする必要があります。


6
Javaまたは.NETコードの場合、DIPはどのように正確にそれを行いますか?これらの言語では、C ++とは異なり、低レベルモジュールの実装を変更しても、それを使用する高レベルモジュールを変更する必要はありません。パブリックインターフェースの変更のみが波及しますが、上位レベルで定義された抽象化も変更する必要があります。
ロジェリオ

5

依存関係の逆転の原則を明確にする方法は次のとおりです。

複雑なビジネスロジックをカプセル化するモジュールは、ビジネスロジックをカプセル化する他のモジュールに直接依存すべきではありません。代わりに、単純なデータへのインターフェースのみに依存する必要があります。

つまり、Logic通常のようにクラスを実装する代わりに、

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

あなたは次のようなことをするべきです:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

DataとでDataFromDependencyLogicなく、と同じモジュールに存在する必要がありますDependency

なぜこれを行うのですか?

  1. 2つのビジネスロジックモジュールが分離されました。ときにDependency変更が、あなたは変更する必要はありませんLogic
  2. 何をするかを理解することLogicは、はるかに簡単なタスクです。ADTのように見えるものだけを操作します。
  3. Logicより簡単にテストできるようになりました。Data偽のデータで直接インスタンス化して渡すことができるようになりました。モックや複雑なテストの足場は必要ありません。

これは正しくないと思います。場合はDataFromDependencyこれを直接参照する、Dependencyとして、同じモジュールでありLogic、その後、Logicモジュールはまだ直接に依存してDependency、コンパイル時にモジュール。パー原則のおじさんボブの説明、それを回避することはDIPの全体のポイントです。むしろ、DIPをフォローするにDataは、と同じモジュールLogicにあるDataFromDependency必要がありますが、と同じモジュールにある必要がありますDependency
マークアメリー

3

制御の反転(IoC)は、オブジェクトが依存関係をフレームワークに要求するのではなく、外部のフレームワークから依存関係を渡される設計パターンです。

従来のルックアップを使用した疑似コードの例:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

IoCを使用した同様のコード:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

IoCの利点は次のとおりです。

  • 中央フレームワークに依存しないため、必要に応じてこれを変更できます。
  • オブジェクトはインジェクションによって、できればインターフェースを使用して作成されるため、依存関係をモックバージョンに置き換える単体テストを簡単に作成できます。
  • コードの分離。

依存関係の逆転の原理と制御の逆転は同じものですか?
Peter Mortensen

3
DIPで「反転」ではない制御の反転と同じ。1つ目はコンパイル時の依存関係に関するもので、2つ目は実行時のメソッド間の制御のフローに関するものです。
ホジェリオ

IoCバージョンに何か欠けているような気がします。それがどのDatabaseオブジェクトを定義するのか、それはどこから来るのか。私はこれがどのようにすべての依存関係を巨大な神の依存関係のトップレベルに指定するのを遅らせるだけではないのかを理解しようとしています
Richard Tingle

1
@Rogérioですでに述べたように、DIPはDI / IoCではありません。この答えは間違っていますIMO
zeraDev

1

依存関係の逆転のポイントは、再利用可能なソフトウェアを作成することです。

アイデアは、2つのコードが相互に依存する代わりに、いくつかの抽象化されたインターフェースに依存するというものです。その後、片方をもう片方なしで再利用できます。

これを最も一般的に実現する方法は、JavaのSpringのような制御(IoC)コンテナーの反転によるものです。このモデルでは、オブジェクトが出て依存関係を見つけるのではなく、オブジェクトのプロパティがXML構成を介して設定されます。

この疑似コードを想像してみてください...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClassは、ServiceクラスとServiceLocatorクラスの両方に直接依存しています。別のアプリケーションで使用する場合は、両方が必要です。これを想像してみてください...

public class MyClass
{
  public IService myService;
}

現在、MyClassはIServiceインターフェイスという単一のインターフェイスに依存しています。IoCコンテナーに実際にその変数の値を設定させます。

したがって、MyClassは、他の2つのクラスの依存関係を伴うことなく、他のプロジェクトで簡単に再利用できます。

さらに良いことに、MyServiceの依存関係とそれらの依存関係の依存関係をドラッグする必要はありません。


2
この答えは、実際にはDIPに関するものではなく、他のものです。2つのコードは、互いにではなく、いくつかの抽象化されたインターフェイスに依存することができ、依存関係の逆転の原則には従いません。
ホジェリオ

1

企業の「高レベル」の従業員が彼らの計画の実行に対して支払われ、これらの計画が多くの「低レベル」の従業員の計画の総計の実行によって提供されると仮定すると、次のように言うことができます。高レベルの従業員の計画の説明が何らかの方法で低レベルの従業員の特定の計画に結び付けられている場合、それは一般的にひどい計画です。

上級幹部が「配達時間を改善する」計画を持っており、配送ラインの従業員が毎朝コーヒーを飲んでストレッチをしなければならないことを示している場合、その計画は結合度が高く、凝集度が低くなります。しかし、計画が特定の従業員について言及しておらず、実際に単に「作業を実行できるエンティティーが機能するように準備されている」ことを要求する場合、計画は疎結合でより凝集性があります。計画は重複せず、簡単に置き換えることができます。請負業者、またはロボットは、従業員を簡単に置き換えることができ、高レベルの計画は変更されません。

依存関係逆転原理の「高レベル」は「より重要」を意味します。


1
これは非常に良いアナロジーです。DICでは、高レベルのコンポーネントは実行時に低レベルのコンポーネントに依拠しますが、この例では、高レベルのプランの実行は低レベルのプランに依存します。しかし、DIC の場合と同様に、コンパイル時に高レベルのプランに依存するのは低レベルのプランであり、アナロジーでは、低レベルのプランの形成が高レベルのプランに依存することになります。
マークアメリー

0

上記の回答で良い説明がなされているのが分かります。ただし、簡単な例を使用して簡単な説明を提供したいと思います。

依存関係の逆転の原則により、プログラマーはハードコーディングされた依存関係を削除して、アプリケーションを疎結合にして拡張可能にすることができます。

これを達成する方法:抽象化を通じて

依存関係の逆転なし:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

上記のコードスニペットでは、アドレスオブジェクトはハードコーディングされています。代わりに、依存関係の逆転を使用し、コンストラクターまたはセッターメソッドを介してアドレスオブジェクトを挿入できる場合。どれどれ。

依存関係の反転あり:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}

-1

依存関係の逆転:具体化ではなく、抽象化に依存します。

制御の反転:メインvs抽象化、およびメインがシステムの接着剤である方法。

DIPおよびIoC

これらはこれについて話すいくつかの良い記事です:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/


-1

2つのクラスがあるEngineerとしProgrammerます:と:

クラスエンジニアは、以下のようにプログラマクラスに依存しています。

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

この例でEngineerは、クラスはクラスに依存していますProgrammer。変更する必要がある場合はどうなりProgrammerますか?

明らかに私Engineerも変更する必要があります。(うわー、この時点でOCPも違反されています)

次に、この混乱を解消するために何が必要なのでしょうか。答えは実際には抽象化です。抽象化により、これら2つのクラス間の依存関係を削除できます。たとえばInterface、Programmerクラスのを作成できます。これからProgrammerは、を使用Interfaceするすべてのクラスでを使用する必要があります。次に、Programmerクラスを変更することにより、それを使用したクラスを変更する必要はありません。中古。

注:DependencyInjectionを行うのDIPSRPも役立ちます。


-1

一般的に良い答えの急増に加えて、自分自身の小さなサンプルを追加して、良い実践と悪い実践を実証したいと思います。そして、はい、私は石を投げる人ではありません!

たとえば、小さなプログラムでコンソールI / Oを介して文字列をbase64形式変換するとします。これは素朴なアプローチです:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

DIPは基本的に、高レベルのコンポーネントは低レベルの実装に依存するべきではないと述べています。「レベル」はRobert C. MartinによるI / Oからの距離です(「クリーンアーキテクチャ」)。しかし、どのようにしてこの苦境から抜け出しますか?中央のエンコーダーを、インターフェースの実装方法を気にすることなく、インターフェースのみに依存させるだけで、

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

GoodEncoderI / Oモードを変更するために触れる必要がないことに注意してください。そのクラスは、知っているI / Oインターフェースに満足しています。低レベルの実装でIReadableIWriteableそれを気にすることはありません。


これは依存関係の反転ではないと思います。結局のところ、ここでは依存関係を反転していません。GoodEncoder2番目の例では、リーダーとライターは依存しません。DIPの例を作成するには、ここで抽出したインターフェイスを「所有」する概念を導入する必要があります。特に、実装を外部に残しながら、それらをGoodEncoderと同じパッケージに配置する必要があります。
マークアメリー

-2

依存関係逆転原理(DIP)によると

i)高レベルのモジュールは低レベルのモジュールに依存してはなりません。どちらも抽象化に依存する必要があります。

ii)抽象化は詳細に依存してはならない。詳細は抽象化に依存する必要があります。

例:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

注:クラスは、特定の詳細(インターフェースの実装)ではなく、インターフェースまたは抽象クラスのような抽象化に依存する必要があります。

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