web-dev-qa-db-ja.com

なぜ依存性注入を使用する必要があるのですか?

依存性注入 を使用する理由に関するリソースを探すのに苦労しています。私が目にするほとんどのリソースは、オブジェクトのインスタンスをオブジェクトの別のインスタンスに渡すだけであると説明していますが、なぜですか?これはよりきれいなアーキテクチャ/コードのためだけですか、それとも全体としてパフォーマンスに影響しますか?

なぜ次のことをする必要があるのですか?

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

次の代わりに?

class Profile {
    public function deactivateProfile()
    {
        $setting = new Setting();
        $setting->isActive = false;
    }
}
99
Daniel

利点は、依存関係の注入がない場合、プロファイルクラス

  • 設定オブジェクトの作成方法を知っている必要がある(単一責任の原則に違反)
  • 常に同じ方法で設定オブジェクトを作成します(2つの間に緊密な結合を作成します)

しかし、依存性注入で

  • 設定オブジェクトを作成するロジックはどこか別の場所にあります
  • さまざまな種類の設定オブジェクトを簡単に使用できます

これはこの特定のケースでは無関係に見えるかもしれません(または場合によってはそうかもしれません)が、設定オブジェクトについて話しているのではなく、ファイルにデータを保存するものとそれを保存する別の実装を持つDataStoreオブジェクトについて考えていると想像してくださいデータベース。また、自動テストの場合は、モック実装も必要です。これで、Profileクラスが使用するハードコードを実際にハードコードしたくなくなりました。さらに重要なのは本当に、本当に Profileクラスにファイルシステムパス、DB接続、およびパスワードについて知らせたくない場合です。なので、DataStoreオブジェクトの作成hasは別の場所で発生します。

104

依存性注入により、コードのテストが容易になります。

MagentoのPaypalインテグレーションでキャッチが難しいバグを修正する任務を課されたとき、私はこれを直接体験しました。

PaypalがMagentoに支払いの失敗について通知したときに問題が発生しました。Magentoは失敗を適切に登録しませんでした。

潜在的な修正を「手動で」テストするのは非常に退屈な作業です。「失敗した」Paypal通知を何らかの形でトリガーする必要があります。 e-checkを送信してキャンセルし、エラーが発生するまで待つ必要があります。つまり、1文字のコード変更をテストするには3日以上かかります。

幸いにも、この機能を開発したMagentoコア開発者はテストを念頭に置いており、依存性注入パターンを使用してそれを簡単にしたようです。これにより、次のような簡単なテストケースで作業を検証できます。

<?php
// This is the dependency we will inject to facilitate our testing
class MockHttpClient extends Varien_Http_Adapter_Curl {
    function read() {
        // Make Magento think that Paypal said "VERIFIED", no matter what they actually said...
        return "HTTP/1.1 200 OK\n\nVERIFIED";
    }
}

// Here, we trick Magento into thinking Paypal actually sent something back.
// Magento will try to verify it against Paypal's API though, and since it's fake data, it'll always fail.
$ipnPayload = array (
  'invoice'        => '100058137',         // Order ID to test against
  'txn_id'         => '04S87540L2309371A', // Test Paypal transaction ID
  'payment_status' => 'Failed'             // New payment status that Magento should ingest
);

// This is what Magento's controller calls during a normal IPN request.
// Instead of letting Magento talk to Paypal, we "inject" our fake HTTP client, which always returns VERIFIED.
Mage::getModel('Paypal/ipn')->processIpnRequest($ipnPayload, new MockHttpClient());

DIパターンには他にも多くの利点があると思いますが、テスト可能性の向上は私の心の中で単一の最大の利点です

この問題の解決策に興味がある場合は、こちらのGitHubリポジトリを確認してください。 https://github.com/bubbleupdev/BUCorefix_Paypalstatus

64
Eric Seastrand

なぜですか?

なぜ依存性注入を使用する必要があるのですか?

これについて私が見つけた最高のニーモニックは " new is glue "です:コードでnewを使用するたびに、そのコードはその特定の実装に関連付けられます=。コンストラクタでnewを繰り返し使用すると、特定の実装のチェーンが作成されます。また、クラスを構築せずにインスタンスを「持つ」ことはできないため、そのチェーンを分離することはできません。

例として、あなたがレースカーのビデオゲームを書いていると想像してください。クラスGameから始めて、RaceTrackを作成します。これにより、8つのCarsが作成され、それぞれがMotorを作成します。ここで、4つのCarsを別のアクセラレーションで追加したい場合は、[すべてのクラスについてを変更する必要があります。ただし、Gameは除きます。

よりクリーンなコード

これはよりきれいなアーキテクチャ/コードのためだけですか

はい

ただし、この状況ではわかりにくいのように見えるかもしれません。これは方法のより多くの例であるためです。実際の利点は、いくつかのクラスが関係する場合にのみ示され、デモンストレーションがより困難ですが、前の例でDIを使用したと想像してください。これらすべてを作成するコードは、次のようになります。

List<Car> cars = new List<Car>();
for(int i=0; i<8; i++){
    float acceleration = 0.3f;
    float maxSpeed = 200.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
RaceTrack raceTrack = new RaceTrack(cars);
Game game = new Game(raceTrack);

これらの4つの異なる車を追加するには、次の行を追加します。

for(int i=0; i<4; i++){
    float acceleration = 0.5f;
    float maxSpeed = 100.0f;
    Motor motor = new Motor(acceleration, maxSpeed);
    Car car = new Car(motor);
    cars.Add(car);
}
  • RaceTrackGameCar、またはMotorを変更する必要はありませんでした。つまり、新しいバグが発生していないことを100%確認できます。そこ!
  • 複数のファイル間をジャンプする代わりに、画面上で完全な変更を同時に確認できます。これは、作成/設定/構成を独自のものとして見た結果です 責任 -モーターを構築するのは自動車の仕事ではありません。

パフォーマンスに関する考慮事項

それともパフォーマンス全体に影響しますか?

いいえ。しかし、あなたに完全に正直に言うと、そうかもしれません。

しかし、その場合でも、あなたが気にする必要がないほど途方もなく少ない量です。将来のある時点で、5MhzのCPUと2MBのRAMに相当するtamagotchiのコードを記述する必要がある場合、maybeあなたmightはこれに注意する必要があります。

99.999%*のケースでは、バグの修正に費やす時間が減り、リソースを大量に消費するアルゴリズムの改善により多くの時間を費やすため、パフォーマンスが向上します。

* 完全に構成された数

追加情報:「ハードコード」

間違いありません。これはisであり、依然として「ハードコード」されています-数値はコードに直接書き込まれます。ハードコード化されていないということは、それらの値をテキストファイルに保存するようなものを意味します。 JSON形式で-そして、それらをそのファイルから読み取ります。

これを行うには、ファイルを読み取ってJSONを解析するためのコードを追加する必要があります。例をもう一度考えてみると、非DIバージョンでは、CarまたはMotorがファイルを読み取る必要があります。それはあまり意味がないように聞こえません。

DIバージョンでは、ゲームを設定するコードに追加します。

26
R. Schmitz

私は常に依存性注入に困惑していました。それはJava=球の内部にのみ存在するように見えましたが、それらの球は大きな敬意をもってそれについて話しました。それは、混乱を秩序をもたらすと言われている素晴らしいパターンの1つでした。しかし、例は常に複雑で人工的なものであり、問​​題ではないものを確立してから、コードをより複雑にすることで問題を解決しようとしました。

Python devがこの知恵を私に伝えたとき、それはより理にかなっています。それは単に関数に引数を渡すだけです。それはまったくパターンではなく、あなたに思い出させるようなものcan引数として何かを要求しますたとえあったとしてもあなた自身が妥当な値を提供したと考えられます。

したがって、あなたの質問は「なぜ関数が引数を取る必要があるのか​​」とほぼ同じです。そして、同じ答えがたくさんあります。つまり、発信者に決定をさせるです。

もちろん、これにはコストが伴います。現在、あなたはforcing呼び出し側がsomeを決定しているためです(引数をオプションにしない限り)。また、インターフェイスがやや多いため繁雑。その代わり、柔軟性が得られます。

それで、この特定のSettingタイプ/値を具体的に使用する必要がある正当な理由はありますか?コードの呼び出しで別のSettingタイプ/値が必要になる可能性があるのに、正当な理由はありますか? (覚えておいてくださいテストはコードです!)

17
Eevee

私はこのパーティーに遅れて来るのを知っていますが、重要なポイントが逃されていると感じています。

なぜ私はこれをするべきですか:

class Profile {
    public function deactivateProfile(Setting $setting)
    {
        $setting->isActive = false;
    }
}

すべきではない。しかし、依存性注入は悪い考えではありません。これは間違っているからです。

コードを使用してこのことを見てみましょう。これを実行します。

$profile = new Profile();
$profile->deactivateProfile($setting);

私たちがこれから同じことを得るとき:

$setting->isActive = false; // Deactivate profile

もちろん、それは時間の無駄のようです。このようにするときです。これは、依存性注入の最適な使用方法ではありません。それはクラスの最良の使用でさえありません。

今度は代わりにこれがあったらどうなるでしょう:

$profile = new Profile($setting);

$application = new Application($profile);

$application.start();

そして、applicationは、実際に変化しているprofileについて特に何も知る必要なく、settingを自由にアクティブ化および非アクティブ化できます。なぜそれが良いのですか?設定を変更する必要がある場合。 applicationはこれらの変更から守られているため、何かに触れるとすぐにすべてが壊れるのを監視する必要がなく、安全な閉じ込められたスペースで自由に動けます。

これは 動作とは別の構造 の原則に従います。ここのDIパターンは単純なものです。必要なすべてをできるだけ低いレベルで構築し、それらを一緒に配線して、すべての動作が1回の呼び出しで動き始めます。

その結果、何が何に接続するかを決定する別の場所と、何が何に何を言うかを管理する別の場所が得られます。

時間の経過とともに維持する必要のあるものでそれを試して、それが役に立たないかどうかを確認してください。

7
candied_orange

あなたが与える例は、古典的な意味での依存性注入ではありません。依存関係注入は通常、コンストラクターでオブジェクトを渡すこと、またはオブジェクトが作成された直後に「セッター注入」を使用して、新しく作成されたオブジェクトのフィールドに値を設定することを指します。

この例では、オブジェクトを引数としてインスタンスメソッドに渡します。次に、このインスタンスメソッドは、そのオブジェクトのフィールドを変更します。依存性注入?いいえ。カプセル化とデータ非表示を解除しますか?絶対に!

ここで、コードが次のようである場合:

class Profile {
    private $settings;

    public function __construct(Settings $settings) {
        $this->settings = $settings;
    }

    public function deactive() {
        $this->settings->isActive = false;
    }
}

次に、依存性注入を使用していると言います。顕著な違いは、コンストラクターに渡されるSettingsオブジェクトまたはProfileオブジェクトです。

これは、設定オブジェクトの作成に費用がかかるか複雑である場合、または設定がランタイム動作を変更するために複数の具象実装が存在するインターフェースまたは抽象クラスである場合に役立ちます。

メソッドを呼び出すのではなく、Settingsオブジェクトのフィールドに直接アクセスしているため、依存関係注入の利点の1つであるポリモーフィズムを利用できません。

プロファイルの設定はそのプロファイルに固有のようです。この場合、次のいずれかを行います。

  1. プロファイルコンストラクター内で設定オブジェクトをインスタンス化する

  2. コンストラクターで設定オブジェクトを渡し、プロファイルに適用される個々のフィールドをコピーします

正直なところ、SettingsオブジェクトをdeactivateProfileに渡してから、Settingsオブジェクトの内部フィールドを変更すると、コードのにおいがします。 Settingsオブジェクトは、その内部フィールドを変更する唯一のものでなければなりません。

7
Greg Burghardt

顧客として、車に何かをするためにメカニックを雇ったとき、そのメカニックが最初から車を組み立てて、それで作業することを期待していますか?いいえ、あなたはメカニックに働きたい車を与えてください

ガレージのオーナーとして、整備士に車に何かをするように指示すると、整備士は自分のドライバー/レンチ/自動車部品を作成することを期待しますか?いいえ、あなたは整備士に彼が使用する必要がある部品/ツールを提供します

なぜこれを行うのですか?さて、それについて考えてください。あなたは誰かを雇って整備士になりたいガレージのオーナーです。あなたはそれらを整備士であることを教えます(=あなたはコードを書くでしょう)。

何が簡単になるでしょう:

  • ドライバーを使ってスポイラーを車に取り付ける方法を整備士に教えます。
  • メカニックに車の作成、スポイラーの作成、ドライバーの作成を教えてから、新しく作成したスポイラーを、新しく作成したドライバーで新しく作成した車に取り付けます。

メカニックにすべてをゼロから作成させないことには、大きなメリットがあります。

  • 言うまでもなく、既存のツールと部品を整備士に提供するだけの場合、トレーニング(=開発)は劇的に短縮されます。
  • 同じメカニックが同じ作業を複数回実行する必要がある場合は、常に古いドライバーを捨てて新しいドライバーを作成する代わりに、ドライバーがドライバーを再利用することを確認できます。
  • さらに、すべてを作成することを学んだ整備士は、より専門家である必要があり、したがって、より高い賃金を期待します。ここでのコーディングの類似点は、多くの責任を持つクラスは、厳密に定義された単一の責任を持つクラスよりも維持するのがはるかに難しいことです。
  • さらに、新しい発明が市場に出て、スポイラーがプラスチックではなくカーボンから作られているとき。あなたはretrain(=再開発)あなたの専門家のメカニックでなければなりません。ただし、スポイラーを同じ方法で取り付けることができる限り、「単純な」メカニックを再訓練する必要はありません。
  • 自分で作った車に依存しないメカニックがいるということは、あなたが対応できるメカニックがいるということですany car彼らは受け取ることができます。整備士の訓練時にはまだ存在していなかった車も含まれます。ただし、あなたのエキスパートメカニックは、作成された新しい車を作成することはできません彼らのトレーニング。

エキスパートメカニックを雇って訓練すると、コストがかかり、単純な仕事であるべきことを実行するのにより多くの時間がかかり、彼らのいずれかがいつでも再訓練される必要がある従業員がいることになります多数責任を更新する必要があります。

開発の類似点は、ハードコードされた依存関係を持つクラスを使用する場合、オブジェクトの新しいバージョン(この場合はSettings)が作成されるたびに継続的な再開発/変更が必要になるクラスを維持するのが困難になるということです。さまざまなタイプのSettingsオブジェクトを作成できるようにするには、クラスの内部ロジックを開発する必要があります。

さらに、クラスを使用する人は、クラスに渡したいSettingsオブジェクトを単純に渡すことができるのではなく、クラスをaskクラスで正しいSettingsオブジェクトを作成する必要があります。これは、適切なツールを作成するようにクラスに依頼する方法を理解するための消費者向けの追加の開発を意味します。


はい、依存関係の逆変換では、依存関係をハードコーディングするのではなく、書くのに少し手間がかかります。はい、さらに入力するのは面倒です。

しかし、これは「変数の宣言にはより多くの労力がかかる」ため、リテラル値をハードコード化することを選択するのと同じ議論です。技術的には正しいですが、プロは短所を数桁上回っています

アプリケーションの最初のバージョンを作成するときには、依存関係の逆転の利点はありません。依存関係の逆転の利点は、その初期バージョンを変更または拡張する必要がある場合に経験されます。また、最初は正しく理解でき、コードを拡張または変更する必要がないと思い込ませないでください。物事を変える必要があります。


これは全体としてパフォーマンスに影響しますか?

これは、アプリケーションの実行時パフォーマンスには影響しません。しかし、それはdeveloperの開発時間(およびパフォーマンス)に大きな影響を与えます。

6
Flater

すべてのパターンと同様に、肥大化したデザインを避けるために「なぜ」と尋ねることは非常に有効です。

依存性注入の場合、これは間違いなく、2つの、OOP設計の最も重要な側面...

低カップリング

コンピュータプログラミングの結合

ソフトウェアエンジニアリングでは、結合はソフトウェアモジュール間の相互依存度です。 2つのルーチンまたはモジュールがどれだけ密接に接続されているかの尺度。モジュール間の関係の強さ。

lowカップリングを実現したい。 2つの要素が強く結び付いているということは、一方を変更すると、もう一方も変更しなければならない可能性が高いということです。一方のバグまたは制限は、もう一方のバグ/制限を引き起こす可能性があります。等々。

他のオブジェクトをインスタンス化する1つのクラスは非常に強い結合です。これは、1つのクラスが他の存在を知る必要があるためです。インスタンス化の方法(コンストラクタが必要とする引数)を知る必要があり、コンストラクタを呼び出すときにこれらの引数が利用可能である必要があります。また、言語が明示的な分解(C++)を必要とするかどうかに応じて、これはさらに複雑になります。新しいクラス(つまり、NextSettingsなど)を導入する場合は、元のクラスに戻って、コンストラクターへの呼び出しを追加する必要があります。

高い凝集力

凝集度

コンピュータプログラミングでは、凝集度はモジュール内の要素が一緒に属している程度を指します。

これはコインの裏側です。コードの1つのユニット(1つのメソッド、1つのクラス、1つのパッケージなど)を見る場合、そのユニット内のすべてのコードができる限り少ない責任を持つようにする必要があります。

これの基本的な例はMVCパターンです。ドメインモデルをビュー(GUI)から明確に分離し、それらを結び付ける制御レイヤーを使用します。

これにより、さまざまなことを行う大きなチャンクを取得するコードの膨張を回避できます。その一部を変更したい場合は、他のすべての機能も追跡し、バグなどを回避する必要があります。そして、あなたはすぐに抜け出すのが非常に難しい穴に自分をプログラムします。

依存関係注入を使用すると、DIを実装するクラス(または構成ファイル)への依存関係の作成または追跡を委任できます。他のクラスは正確に何が行われているのかについてあまり気にしません-それらはいくつかの一般的なインターフェイスで動作し、実際の実装が何であるかを知りません、つまりそれらは責任を負いません他のもののために。

0
AnoE