質問

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

役に立ちましたか?

解決

この文書を確認してください: 依存関係逆転の原則.

それは基本的に次のように述べています:

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

なぜそれが重要なのかというと、簡単に言うと次のとおりです。変更にはリスクが伴いますが、実装ではなく概念に依存することで、コール サイトでの変更の必要性が減ります。

事実上、DIP は異なるコード部分間の結合を軽減します。たとえば、伐採施設を実装するにはさまざまな方法がありますが、その使用方法は時間の経過とともに比較的安定している必要があるという考えです。ロギングの概念を表すインターフェイスを抽出できれば、このインターフェイスはその実装よりも時間の経過とともにはるかに安定し、呼び出しサイトはそのロギング メカニズムの維持または拡張中に加えられる変更の影響をはるかに少なくなるはずです。

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

他のヒント

『Agile Software Development, Principles, Patterns, and Practices』および『Agile Principles, Patterns, and Practices in C#』という書籍は、依存関係反転原則の背後にある本来の目標と動機を完全に理解するための最良のリソースです。「依存性逆転の原則」という記事も優れたリソースですが、これは最終的に前述の書籍に掲載された草案の要約版であるため、依存関係の逆転の概念に関する重要な議論がいくつか省略されています。パッケージとインターフェイスの所有権は、書籍『デザイン パターン』 (Gamma et.アル)。

要約すると、依存関係逆転の原則は主に次のことを目的としています。 逆転する 「下位レベル」コンポーネントがインターフェースに依存するような、「上位レベル」コンポーネントから「下位レベル」コンポーネントへの依存関係の従来の方向 所有している 「より高いレベル」のコンポーネントによって。(注記:ここでの「高レベル」コンポーネントとは、外部の依存関係/サービスを必要とするコンポーネントを指し、必ずしも階層化されたアーキテクチャ内での概念的な位置を意味するものではありません。)その際、結合は行われません。 削減 それくらい ずれた 理論的に価値の低いコンポーネントから、理論的に価値の高いコンポーネントまで。

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

依存関係逆転の原則で言及されていないのは、インターフェイス (例:MyService → [ILogger ⇐ Logger])。これにより、依存関係の特定の実装の詳細からコンポーネントが切り離されますが、コンシューマーと依存関係の間の関係が逆転するわけではありません (例:[MyService → IMyServiceLogger] ⇐ ロガー。

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

この再利用の一般的な目標の中で、再利用の 2 つのサブタイプを説明できます。

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

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

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

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

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

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

この原則については、インターフェイス、依存関係の注入、分離インターフェイス パターンの単純な使用に関連する詳細な説明を参照してください。 ここ. 。さらに、この原則が JavaScript などの動的型付け言語にどのように関連するかについての説明も見つかります。 ここ.

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

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

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

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

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

私にとって、依存関係逆転の原則は、次のとおりです。 公式記事, これは、本質的に再利用性が低いモジュールの再利用性を高めるという誤った試みであり、C++ 言語の問題を回避する方法でもあります。

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

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

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

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

従来の階層型アーキテクチャ

従来、階層化されたアーキテクチャの UI はビジネス層に依存し、さらにビジネス層はデータ アクセス層に依存していました。

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

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

データ アクセス層用のライブラリまたはパッケージが必要になります。

// 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;
}

抽象化に依存していますが、ビジネスとデータ アクセス間の依存関係は変わりません。

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

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

まずドメイン層とは何かを定義し、その通信の抽象化が永続性と定義されます。

// Domain.dll
public interface IProductRepository;

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

永続層がドメインに依存した後、依存関係が定義されている場合は反転するようになりました。

// Persistence.dll
public class ProductDAO : IProductRepository{

}

http://xurxodev.com/content/images/2016/02/Dependency-Inversion-Layers.png

原理の深化

コンセプトをよく理解し、目的とメリットを深めていくことが重要です。機械的に留まって典型的な事例リポジトリを学習すると、依存の原則をどこに適用できるかを特定できなくなります。

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

そんなに普通に より安定性の低いものに依存しない、最も安定したものをより頻繁に変更できるようになります。

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

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

アーキテクチャ

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

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

クリーンなアーキテクチャ ドメインは中央に位置しており、依存関係を示す矢印の方向を見ると、何が最も重要で安定した層であるかが明らかです。外側のレイヤーは不安定なツールであると考えられるため、依存しないようにしてください。

六角形の建築

これはヘキサゴナル アーキテクチャでも同様に発生します。ドメインは中央部分に配置され、ポートはドミノから外部への通信を抽象化したものです。ここでも、ドメインが最も安定しており、従来の依存関係が逆転していることが明らかです。

基本的には次のように言われます。

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

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

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

つまり、クラスを実装する代わりに 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 そして DataFromDependency と同じモジュールに存在する必要があります Logic, 、 ではない Dependency.

なぜこれを行うのでしょうか?

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

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

理由 浸漬 重要なのは、OO 原則の「疎結合設計」を保証するためです。

ソフトウェア内のオブジェクトは、一部のオブジェクトが最上位のオブジェクトであり、下位レベルのオブジェクトに依存する階層に入るべきではありません。下位レベルのオブジェクトの変更は最上位のオブジェクトに波及するため、ソフトウェアは変更に対して非常に脆弱になります。

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

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

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

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

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

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

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

  • 中心的なフレームワークに依存していないため、必要に応じて変更できます。
  • オブジェクトはインターフェイスを使用して注入によって作成されるため、依存関係をモックバージョンに置き換えるユニットテストを簡単に作成できます。
  • オフコードのデカップリング。

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

その考え方は、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 コンテナにその変数の値を設定させます。

これで、他の 2 つのクラスの依存関係を引き継ぐことなく、MyClass を他のプロジェクトで簡単に再利用できるようになりました。

さらに良いことに、MyService の依存関係、およびそれらの依存関係の依存関係、および...まあ、それはわかりますね。

コントロールコンテナの反転と依存性注入パターン マーティン・ファウラー著も良い読み物です。見つけました ヘッドファーストのデザインパターン DI やその他のパタ​​ーンを学ぶための最初の一歩として最適な本です。

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

制御の反転:メインと抽象化、そしてメインがどのようにシステムの接着剤であるか。

DIP and 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/

依存性反転原則 (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();
        }
    }

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top