web-dev-qa-db-ja.com

ほぼ全員が共通のデータ構造へのアクセスを必要とする場合の依存性注入の利点は何ですか?

OOPでグローバル 悪である には多くの理由があります。

共有が必要なオブジェクトの数またはサイズが多すぎて、関数パラメーターで効率的に渡すことができない場合、通常は、グローバルオブジェクトではなくDependency Injectionを推奨します。

しかし、ほとんどの人が特定のデータ構造について知る必要がある場合、依存性注入がグローバルオブジェクトよりも優れているのはなぜですか?

(特定のアプリケーションを深く掘り下げることなく、ポイントを一般的に示すための簡略化されたもの)

タイプ、名前、色、速度、位置など、非常に多くのプロパティと状態を持つ多数の仮想車両があります。多くのユーザーがそれらをリモートコントロールでき、膨大な数のイベント(両方のユーザー-開始され、自動)は、それらの状態またはプロパティの多くを変更できます。

素朴な解決策は、それらのグローバルコンテナーを作成することです。

vector<Vehicle> vehicles;

どこからでもアクセスできます。

よりOOPにやさしいソリューションは、コンテナーをメインイベントループを処理するクラスのメンバーにし、そのコンストラクターでインスタンス化することです。それを必要とし、メインスレッドのメンバーであるすべてのクラスは、コンストラクターのポインターを介してコンテナーにアクセスできます。たとえば、ネットワーク接続を介して外部メッセージが着信した場合、解析を処理するクラス(接続ごとに1つ)が引き継ぎ、パーサーはポインターまたは参照を介してコンテナーにアクセスできます。解析されたメッセージがコンテナの要素に変化をもたらすか、アクションを実行するためにそこからいくつかのデータを必要とする場合、信号とスロットを介して数千もの変数を投げる必要なく処理できます(またはさらに悪いことに、それらをパーサーに格納して、後でパーサーを呼び出した人が取得できるようにします。もちろん、依存性注入を通じてコン​​テナへのアクセスを受け取るすべてのクラスは、同じスレッドの一部です。さまざまなスレッドが直接それにアクセスすることはありませんが、その仕事をしてからシグナルをメインスレッドに送信し、メインスレッドのスロットがコンテナーを更新します。

しかし、クラスの大部分がコンテナにアクセスする場合、それがグローバルと本当に異なるのは何ですか?非常に多くのクラスがコンテナ内のデータを必要とする場合、「依存性注入方法」は偽装されたグローバルではないですか?

1つの答えはスレッドの安全性です。私はグローバルコンテナーを悪用しないように注意していますが、近い将来の別の開発者は、期限が迫っていますが、グローバルコンテナーを別のスレッドで使用します。衝突のケース。ただし、依存性注入の場合でも、別のスレッドで実行されている誰かにポインターを与えると、同じ問題が発生する可能性があります。

20
vsz

ほとんどの人が特定のデータ構造について知る必要がある場合、依存性注入がグローバルオブジェクトよりも優れているのはなぜですか?

依存関係の注入はスライスされたパン以来の最高のものです一方で、グローバルオブジェクトは何十年もの間のソースであることが知られていますすべて悪なので、これはかなり興味深い質問です。

依存性注入のポイントはではないため、リソースを必要とするすべてのアクターがそれを確実に使用できるようにします。明らかに、すべてのリソースをグローバルにすると、すべての俳優がすべてのリソースにアクセスでき、問題が解決されますよね?

依存性注入のポイントは次のとおりです。

  1. アクターがリソースにアクセスできるようにする必要に応じて、および
  2. リソースのインスタンスを制御するには、任意のアクターがアクセスします。

特定の構成ですべてのアクタが偶然に同じリソースインスタンスにアクセスする必要があるという事実は関係ありません。信じてください、あなたはいつかアクターがリソースの異なるインスタンスにアクセスできるように物事を再構成する必要があり、そしてあなたはあなたが隅に自分を描いたことに気付くでしょう。いくつかの回答はすでにそのような構成を示しています:testing

別の例:アプリケーションをクライアントサーバーに分割するとします。クライアント上のすべてのアクターは、クライアント上の同じ中央リソースのセットを使用し、サーバー上のすべてのアクターは、サーバー上の同じ中央リソースのセットを使用します。ここで、ある日、クライアントとサーバーの両方が単一の実行可能ファイルにパッケージ化され、同じ仮想マシンで実行される、クライアントサーバーアプリケーションの「スタンドアロン」バージョンを作成するとします。 (または、選択した言語に応じて、ランタイム環境。)

依存性注入を使用する場合、すべてのサーバーアクターがサーバーリソースインスタンスを受信する一方で、すべてのクライアントアクターに処理対象のクライアントリソースインスタンスが与えられることを簡単に確認できます。

依存性注入を使用しない場合、1つの仮想マシンに存在できるのは各リソースのグローバルインスタンスが1つだけなので、完全に不運になります。

次に、検討する必要があります:すべてのアクターは本当にそのリソースへのアクセスを必要としますか? 本当に?

そのリソースを神のオブジェクトに変換するミスを犯した可能性があります(もちろん、誰もがそれにアクセスする必要があります)、またはプロジェクト内のアクターの数を大幅に過大評価している可能性があります実際にそのリソースへのアクセスが必要です

グローバルを使用すると、アプリケーション全体のソースコードのすべての1行がすべてのグローバルリソースにアクセスできます。依存性注入を使用すると、各リソースインスタンスは実際にそれを必要とするアクターにのみ表示されます。 2つが同じである場合(特定のリソースを必要とするアクターがプロジェクトのソースコードの行の100%を構成している場合)、設計に誤りがあったと考えられます。だから、どちらか

  • その非常に大きな巨大な神のリソースを小さなサブリソースにリファクタリングすることで、さまざまなアクターがそのさまざまな部分にアクセスする必要がありますが、アクターがそのすべての部分を必要とすることはまれです。

  • アクターをリファクタリングして、作業する必要のある問題のサブセットのみをパラメーターとして受け入れるため、常に大きな巨大な中央リソースを参照する必要はありません。

37
Mike Nakis

可変でないグローバルがOOPで悪である理由はたくさんあります。

それは疑わしい主張です。証拠として使用するリンクはstate-mutableグローバルを参照します。彼らは明らかに悪です。読み取り専用グローバルは単なる定数です。定数は比較的正気です。つまり、piの値をすべてのクラスに注入するつもりはありませんか?

共有が必要なオブジェクトの数またはサイズが多すぎて関数パラメーターで効率的に渡すことができない場合、通常、誰もがグローバルオブジェクトではなく依存性注入を推奨します。

いいえ、ありません。

関数/クラスの依存関係が多すぎる場合は、設計がなぜそれほどひどいのかを調べて停止することを推奨します。依存関係が大量にあることは、クラス/関数が多すぎることをしている、または抽象化が不十分であることを示しています。

タイプ、名前、色、速度、位置など、非常に多くのプロパティと状態を持つ多数の仮想車両があります。多くのユーザーがそれらをリモートコントロールでき、膨大な数のイベント(両方のユーザー-開始され、自動)は、その状態またはプロパティの多くを変更できます。

これはデザインの悪夢です。検証の方法や同時実行の制限の方法なしで使用/乱用できるものがたくさんあります-それは単に全体を結合しているだけです。

しかし、クラスの大部分がコンテナにアクセスする場合、それがグローバルと本当に異なるのは何ですか?非常に多くのクラスがコンテナ内のデータを必要とする場合、「依存性注入の方法」は偽装されたグローバルではありませんか?

はい、共通データeverywhereへの結合は、グローバルとあまり変わらないため、依然として悪いです。

利点は、グローバル変数自体からコンシューマーを分離していることです。これにより、インスタンスの作成方法にsome柔軟性が提供されます。また、インスタンスが1つだけになることもありませんforce。さまざまなものを作成して、さまざまな消費者に渡すことができます。また、必要に応じて、コンシューマごとにインターフェースの異なる実装を作成することもできます。

また、ソフトウェアのシフト要件に伴う変更が必然的に伴うため、このような基本的な柔軟性のレベルはvitalです。

39
Telastyn

考慮すべき主な理由は3つあります。

  1. 読みやすさ。コードのすべてのユニットに、注入またはパラメーターとして渡されて処理するために必要なすべてのものが含まれている場合、コードを見て簡単にコードの動作を確認できます。これにより、機能の局所性が得られます。これにより、関心事をより適切に分離できるようになり、また、考えることを強いられます...
  2. Modularity。パーサーは、車両とそのすべてのプロパティのリスト全体を知っている必要があるのですか?それはおそらくありません。たぶん、車両IDが存在するかどうか、または車両XにプロパティYがあるかどうかを照会する必要があるかもしれません。それは、それを行うサービスを作成し、そのサービスのみをパーサーに注入できることを意味します。突然、はるかに意味のあるデザインになり、コードのすべてのビットはそれに関連するデータのみを処理します。それは私たちを導く...
  3. Testability。多くの異なるシナリオに合わせて環境をセットアップする典型的な単体テストの場合、インジェクションによりこれを非常に簡単に実装できます。繰り返しますが、パーサーの例に戻って、パーサー用に作成したすべてのテストケースについて、完全に完成した車両のリスト全体を常に手動で作成しますか?または、前述のサービスオブジェクトのモック実装を作成して、さまざまな数のIDを返すだけですか?どちらを選ぶか知っています。

つまり、DIは目標ではなく、目標の達成を可能にするツールにすぎません。それを誤用した場合(例で示しているように)、目標に近づくことはできません。悪いデザインを良くする魔法のようなDIのプロパティはありません。

16
biziclop

あなたがすでに持っている答えに加えて、

タイプ、名前、色、速度、位置など、非常に多くのプロパティと状態を持つ多数の仮想車両があります。多くのユーザーがそれらをリモートコントロールでき、膨大な数のイベント(両方のユーザー-開始され、自動)は、それらの状態またはプロパティの多くを変更できます。

OOPプログラミングの特徴の1つは、特定のオブジェクトに本当に必要なプロパティのみを保持することです。

オブジェクトに多くのプロパティがある場合-オブジェクトをサブオブジェクトにさらに分解する必要があります。

編集

グローバルオブジェクトがなぜ悪いのかについてはすでに触れましたが、それに1つの例を追加します。

WindowsフォームのGUIコードでユニットテストを実行する必要があります。グローバルを使用する場合は、適切なテストケースを作成するために、すべてのボタンとイベントを作成する必要があります...

3
Mathematics

それは本当にテスト容易性についてです-通常、グローバルオブジェクトは非常に保守可能であり、読み取り/理解が容易です(もちろん、そこにあることを知っていれば)が、永続的なフィクスチャであり、クラスを分離されたテストに分割すると、グローバルオブジェクトが「私について」と表示されたままになっているため、分離テストではグローバルオブジェクトを含める必要があります。ほとんどのテストフレームワークがこのシナリオを簡単にサポートしないことは役に立ちません。

だからはい、それはまだグローバルです。それを持っている根本的な理由は変わっていません、それがアクセスされる方法だけです。

初期化に関しては、これはまだ問題です-テストを実行できるように構成を設定する必要があるため、そこで何も得られません。大きな依存オブジェクトを多数の小さなオブジェクトに分割し、それらを必要とするクラスに適切なオブジェクトを渡すことができると思いますが、メリットよりもさらに複雑になる可能性があります。

これらすべてに関係なく、これは「犬の尻尾を振る」の例であり、テストの必要性は、コードベースの構築方法を推進することです(現在の日時などの静的クラスなどの項目でも同様の問題が発生します)。

個人的には、すべてのグローバルデータをグローバルオブジェクト(またはいくつか)に固定することを好みますが、クラスに必要なビットを取得するためのアクセサーを提供します。次に、DIの複雑さを追加せずに、テストに関して、必要なデータを返すようにそれらを模擬できます。

3
gbjbaanb