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


20

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

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

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

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

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

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

vector<Vehicle> vehicles;

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

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

ただし、クラスの大部分がコンテナにアクセスできるようになった場合、グローバルとはどう違うのでしょうか?非常に多くのクラスがコンテナ内のデータを必要とする場合、「依存性注入方法」は単なる偽装グローバルではありませんか?

答えの1つはスレッドセーフです。グローバルコンテナを乱用しないように注意を払っていますが、近い将来、他の開発者が近い締め切りの圧力の下で、すべてを気にせずに別のスレッドでグローバルコンテナを使用する可能性があります衝突の場合。ただし、依存性注入の場合でも、別のスレッドで実行されている誰かにポインターを与えると、同じ問題が発生する可能性があります。


6
待ってください、あなたは非可変グローバルについて話し、それを正当化するために「なぜグローバル状態が悪いのか」へのリンクを使用しますか?WTF?
テラスティン

1
私はグローバルを正当化するものではありません。グローバルは良い解決策ではないという事実に同意することは明らかだと思います。依存関係の注入を使用しても、グローバルよりもはるかに優れていない(またはほとんど区別できない)状況について話しているだけです。答えはまだこのような状況に依存性注入を使用して、他の隠された利点を指摘するかもしれないので、または他のアプローチは、ジよりも良いだろうということ
VSZ

9
しかし、そこで決定した読み出し専用のグローバルおよびグローバル状態の違いは。
テラスティン


1
@vszではありません-質問は、多くの異なるオブジェクトの問題を回避する方法としてのサービスロケーターファクトリに関するものです。その答えは、クラスに渡されるグローバルデータではなく、グローバルデータへのクラスアクセスを許可するというあなたの質問にも答えます。
gbjbaanb

回答:


37

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

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

依存性注入のポイントは、何らかのリソースを必要とするすべてのアクターがそれを保持できるようにすることだけではありません

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

  1. アクターが必要に応じてリソースアクセスできるようにするため、および
  2. 特定のアクターがアクセスするリソースのインスタンスを制御するため。

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

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

依存性注入を使用すると、すべてのクライアントアクターにクライアントリソースインスタンスが割り当てられ、すべてのサーバーアクターがサーバーリソースインスタンスを受信することを簡単に確認できます。

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

次に、考慮する必要があります。すべてのアクターは本当にそのリソースにアクセスする必要がありますか? 本当に?

そのリソースを神オブジェクトに変えるという間違いを犯した可能性があります(そのため、もちろん誰もがそれにアクセスする必要があります)。 。

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

  • その巨大で巨大な神のリソースを小さなサブリソースにリファクタリングします。そのため、異なるアクターはその異なる部分にアクセスする必要がありますが、アクターがそのすべての部分を必要とすることはめったにありません。

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


私はこれを理解しているかわかりません-一方では、DIはリソースの複数のインスタンスを使用でき、グローバルは使用できないと言います。これは、単一の静的変数を複数のインスタンス化されたオブジェクトと混同しない限り、無意味です-理由はありません「グローバル」は単一のインスタンスまたは単一のクラスでなければなりません。
gbjbaanb

3
@gbjbaanbリソースがZorkであるとします。OPは、システム内のすべてのオブジェクトが動作するためにZorkを必要とするだけでなく、Zorkは1つだけ存在し、すべてのオブジェクトはZorkの1つのインスタンスにのみアクセスする必要があると言っています。私はこれらの2つの仮定のどちらも合理的ではないと言っています:彼のオブジェクトのすべてがZorkを必要とすることはおそらく真実ではなく、一部のオブジェクトはZorkの特定のインスタンスを必要とし、他のオブジェクトはZorkを必要とすることがありますZorkの異なるインスタンス。
マイクナキス

2
@gbjbaanb Zorkの2つのグローバルインスタンスを持つ理由を説明するように頼まれているとは思わない。右?
マイクNakis

1
私はみんなとは言いませんでした、ほぼ全員と言いました。質問のポイントは、DIに、それを必要としない少数のアクターからのアクセスを拒否すること以外の利点があるかどうかでした。「神のオブジェクト」はアンチパターンであることは知っていますが、盲目的にベストプラクティスに従うこともできます。プログラムの全体的な目的が特定のリソースでさまざまなことを行うことである場合があります。その場合、ほぼ全員がそのリソースにアクセスする必要があります。良い例は、画像上で動作するプログラムです。その中のほとんどすべてが画像データに関係しています。
vsz

1
@gbjbaanb私は、クライアントクラスがどちらを取得するかを決定するのではなく、1つのクライアントクラスがZorkAまたはZorkBのいずれかで排他的に動作できるようにすることを考えています。
ユージンリャブツェフ

39

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

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

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

いいえ、そうではありません。

関数/クラスの依存関係が多すぎる場合、停止して、設計がそれほど悪い理由を調べることをお勧めします。依存関係のボートロードがあるということは、クラス/関数が多すぎることを行っているか、十分な抽象化をしていないことを示しています。

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

これは設計上の悪夢です。検証も、並行性の制限もなしで、使用/悪用できるものがたくさんあります。それは、すべてを結合しているだけです。

ただし、クラスの大部分がコンテナにアクセスできるようになった場合、グローバルとはどう違うのでしょうか?非常に多くのクラスがコンテナ内のデータを必要とする場合、「依存性注入方法」は単なる偽装グローバルではありませんか?

はい、どこでも共通のデータに結合することは、グローバルとそれほど違わないので、それでもなお悪いです。

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

また、ソフトウェアの要件の変化に伴い必然的に変化するため、このような基本的なレベルの柔軟性が不可欠です。


「不変」との混乱のため申し訳ありませんが、私は可変と言いたかったのですが、どういうわけか私の間違いを認識しませんでした。
VSZ

「これはデザインの悪夢です」-私は知っています。要件はすべてのそれらのイベントがあることである場合にでもしなければならないデータに変更を行うことができ、その後のイベントは、私は分割し、抽象化の層の下に隠していてもデータに結合されます。全体のプログラムですについて、このデータ。たとえば、プログラムが画像に対して多くの異なる操作を実行する必要がある場合、そのクラスのすべてまたはほぼすべてが何らかの形で画像データに結合されます。「何か違うことをするプログラムを書きましょう」と言うだけでは受け入れられません。
VSZ

2
あるデータベースに定期的にアクセスする多くのオブジェクトを持つアプリケーションは、前例のないものではありません。
ロバートハーヴェイ

@RobertHarvey-確かに、しかし、彼らが依存しているインターフェイスは「データベースにアクセスできる」か「foosの永続データストア」ですか?
テラスティン

1
PHBが500個の機能を要求し、Dilbertがノーと言うDilbertを思い出します。そのため、PHBは1つの機能を要求し、ディルバートは確信しています。翌日、Dilbertは1つの機能につき500件のリクエストを受け取ります。何かが拡大縮小しない場合、どのようにドレスアップしても拡大縮小しないだけです。
corsiKa

16

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

  1. 読みやすさ。コードのすべてのユニットに、注入またはパラメーターとして渡されたものを処理するために必要なものがすべて揃っている場合、コードを見て簡単にコードが何をしているかを簡単に確認できます。これにより、機能の局所性が得られます。これにより、懸念をより適切に分離することも可能になり、考えさせることもできます...
  2. モジュール性。パーサーが車両のリスト全体とそのすべてのプロパティを知っている必要があるのはなぜですか?おそらくそうではありません。車両IDが存在するかどうか、または車両XがプロパティYを持っているかどうかを問い合わせる必要があるかもしれません。突然、あなたははるかに理にかなった設計になり、コードのすべてのビットはそれに関連するデータのみを処理します。それは私たちにつながる...
  3. テスト容易性。典型的な単体テストでは、さまざまなシナリオに合わせて環境をセットアップする必要があり、インジェクションを使用すると非常に簡単に実装できます。繰り返しますが、パーサーの例に戻って、パーサー用に作成するすべてのテストケースについて、常に本格的な車両のリスト全体を常に手作りする必要がありますか?または、前述のサービスオブジェクトの模擬実装を作成して、さまざまなIDを返しますか?私はどちらを選ぶか知っています。

要約すると、DIは目標ではなく、目標を達成するためのツールにすぎません。それを誤用すると(例のように)、目標に近づきません。DIには、悪いデザインを良くする魔法の特性はありません。


メンテナンス問題に焦点を当てた簡潔な回答のために+1。より多くのP:SE回答は、このようなものでなければなりません。
dodgethesteamroller

1
あなたの合計はとても良いです。いいね [今月のデザインパターン]または[今月の流行語]があなたの問題を魔法のように解決すると思うなら、あなたは悪い時間を過ごすでしょう。
corsiKa

@corsiKa私はそれの要点が見えなかったので、私は長い間DI(より正確にはSpring)を嫌っていました。それは、目に見える利益のない非常に多くの面倒のように思えました(これはXML構成の時代に戻っていました)。当時DIが実際に何を購入しているかについてのドキュメントを見つけていたらと思います。しかし、私はそうしなかったので、自分でそれを理解しなければなりませんでした。長い時間かかりました。:)
biziclop

3

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

はい、それはまだグローバルです。アクセスする基本的な理由は変更されておらず、アクセス方法のみが変更されています。

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

それにも関わらず、「犬を振るしっぽ」の例であるテストの必要性は、コードベースの構築方法を駆動することです(現在の日時などの静的クラスなどのアイテムで同様の問題が発生します)。

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


「通常、グローバルオブジェクトは非常に保守しやすい」-可変である場合ではありません。私の経験では、非常に多くの可変グローバル状態を持つことは悪夢である可能性があります(耐えられるようにする方法はありますが)。
sleske

3

すでに答えがあることに加えて、

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

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

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

編集

グローバルオブジェクトが悪い理由については既に述べましたが、1つの例を追加します。

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


確かに、たとえば、パーサーはすべてのことを知る必要があります。なぜなら、何かを変更できるコマンドを受け取る可能性があるからです。
VSZ

1
「実際に質問する前に」...彼の質問に答えますか?
gbjbaanb

@gbjbaanbの質問には多くのテキストがあります。適切に読むまでしばらくお待ちください
数学
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.