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


95

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

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

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

次の代わりに?

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

8
deactivateProfile()にハードコーディングされた依存関係を導入しています(これは悪いことです)。最初のコードにはさらに分離されたコードがあるため、変更とテストが容易になります。
オーリスロンカイネン

3
なぜ最初のものをするのですか?設定を渡し、その値を無視しています。
フィルNデブラン

57
賛成票には同意しません。主題は専門家にとってささいなものと見なされるかもしれませんが、質問にはメリットがあります。依存関係の逆転を使用する必要がある場合、それを使用する正当な理由があるはずです。
18年

13
@PhilNDeBlanc:このコードは明らかに過度に単純化されており、実際のロジックを実際に示すものではありません。ただし、以前の状態を気にせずにをfalseにdeactivateProfile設定するisActiveことが、ここでの正しいアプローチであることを私に示唆します。メソッドを呼び出すということは、現在の(非)アクティブステータスを取得するのではなく、メソッドを非アクティブに設定することを意味します。
18年

2
あなたのコードは、依存性の注入または反転の例ではありません。これは、パラメータ化の例です(多くの場合、DIよりも優れています)。
jpmc26

回答:


104

利点は、依存関係の注入なしで、プロファイルクラス

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

しかし、依存性注入では

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

これは、この特定のケースでは無関係に思えるかもしれませんが、設定オブジェクトではなく、ファイルにデータを保存する別の実装と、データを保存する別の実装を持つDataStoreオブジェクトを考えている場合を想像してくださいデータベース。また、自動化されたテストには、模擬実装も必要です。今、あなたは本当にプロファイルクラスがそれを使用するものをハードコードすることを望まない-そしてさらに重要なことには、あなたは本当にプロファイルクラスがファイルシステムパス、DB接続、パスワードについて知りたくないので、データストアオブジェクトの作成持ってどこか別の場所に発生します。


23
This may seem (or even be) irrelevant in this particular case実際、それは非常に重要だと思います。どのようにして設定を取得しますか?私が見た多くのシステムには、ハードコーディングされたデフォルトの設定セットと公開設定があります。そのため、両方をロードし、一部の値を公開設定で上書きする必要があります。デフォルトの複数のソースが必要になる場合もあります。たぶん、あなたはディスクからいくつかを、DBから他を得るかもしれません。そのため、設定を取得するためのロジック全体は重要な場合が多く、多くの場合、コードを消費することは重要ではありません。
VLAZ

また、Webサービスなどの重要なコンポーネントのオブジェクト初期化により、非常に非$setting = new Setting();効率になることにも言及できます。インジェクションとオブジェクトのインスタンス化は一度行われます。
バイキングスティーブ

9
テストにモックを使用する方がより重要だと思います。コードだけを見ると、それは常にSettingsオブジェクトになり、変更されることはないので、渡すことは無駄な努力のように思えます。ただし、Settingオブジェクトを必要とせずにProfileオブジェクト自体を初めてテストする場合(ソリューションとしてモックオブジェクトを使用する場合)、その必要性は非常に明白です。
-JPhi1618

2
@ JPhi1618「DIは単体テスト用」を強調することの問題は、「なぜ単体テストが必要なのか」という質問につながることだと思います。答えはあなたには明白に思えるかもしれませんが、利点は間違いなくそこにありますが、「この複雑なサウンドを行うには、この複雑なサウンドをする必要があります」と始めたばかりの人にとっては、ターンオフ。そのため、現在行っていることにより適している可能性のあるさまざまな利点に言及するのは良いことです。
IMSoP

1
@ user986730-それは非常に良い点であり、ここでの実際の原則は依存性反転であり、そのインジェクションは1つのテクニックに過ぎないことを強調しています。たとえば、Cでは、IOCライブラリを使用して依存性を実際に注入することはできませんが、モックなどに異なるソースファイルを含めることで、コンパイル時にそれらを反転します。これは同じ効果があります。
スティーブンバーン

64

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

これは、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


4
依存性注入により、依存関係がハードコードされたコードよりもテストが簡単になります。ビジネスロジックから依存関係を完全に排除することはさらに優れています。
Ant P

1
そして、@ AntPが提案することを行う主な方法の1つは、パラメーター化です。データベースから返された結果をページテンプレートの入力に使用されるオブジェクト(一般に「モデル」と呼ばれる)に変換するロジックは、フェッチが発生していることさえ知らないはずです。それらのオブジェクトを入力として取得するだけです。
jpmc26

4
@ jpmc26確かに-私は機能的なコアである命令型シェルについてハープする傾向があります。ドメインは純粋で、モックなしで単体テストでき、永続性、メッセージングなどのようなものに適応するシェルにラップされるだけです。
Ant P

1
テスト容易性のみに焦点を当てることは、DIの採用にとって有害だと思います。テストをあまり必要としないか、すでにテストが制御されていると思う人にとっては魅力的ではありません。DIなしでクリーンで再利用可能なコードを書くことは事実上不可能だと思います。テストは利点のリストのかなり下にあり、この回答がDIの利点に関するすべての質問で1位または2位になっているのは残念です。
カール・Leth

26

なぜ(問題さえ)?

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

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

例として、あなたがレースカーのビデオゲームを書いていると想像してください。まず、クラスGameを作成します。クラスはを作成し、クラスRaceTrackは8 Carsを作成しMotorます。Cars異なるアクセラレーションでさらに4つ追加したい場合、多分を除い言及されたすべてのクラスを変更する必要があります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のコードを作成する必要がある場合は、これを気にする必要があるかもしれませ

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

* 完全に構成された数

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

間違えないでください、これまだ非常に「ハードコーディング」されています-数値はコードに直接書き込まれます。ハードコードされていないということは、これらの値をテキストファイル(たとえばJSON形式)に保存し、そのファイルから値を読み取るようなものを意味します。

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

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


3
広告はハードコードされているため、実際にはコードと設定ファイルにはそれほど違いはありません。動的に読み取る場合でも、アプリケーションにバンドルされているファイルはソースです。値をコードからデータファイルにjsonまたは「config」形式で取得しても、ユーザーが値をオーバーライドするか、環境に依存する必要がある場合を除き、何も役に立ちません。
Jan Hudec

3
私は実際にArduino(16MHz、2KB)で一度たまごっちを作った
Jungkook

@JanHudecはい。私は実際にそこにもっと長い説明がありましたが、それを短くし、それがDIとどう関係するかに焦点を合わせるためにそれを削除することにしました。100%正しくないものが他にもあります。全体的に答えは、DIの「ポイント」を長くしすぎることなくプッシュするように最適化されています。別の言い方をすれば、これは私がDIを使い始めたときに聞きたかったことです。
R.シュミッツ

17

私は常に依存性注入に困惑していました。Javaの領域内にのみ存在するように見えましたが、それらの領域はそれを非常に敬意をもって語っています。それは、秩序を混乱に導くと言われている偉大なパターンの1つでした。しかし、例は常に複雑で人為的であり、問​​題を特定せず、コードをより複雑にすることでそれを解決しようと試みました。

仲間のPython開発者がこの知恵を私に与えたとき、それはより理にかなった:それは単に関数に引数を渡すだけだ。まったくパターンではありません。より多くのあなたが思い出させるようなことができ、引数として何かを頼む場合でも、あなたはおそらく妥当な値を自分で提供している可能性があります。

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

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

だから:この特定のSetting型/値を使用する必要がある具体的な理由はありますか?コードを呼び出すときに別のSetting型/値が必要になる理由はありますか?(テストはコードです!)


2
うん。IoCがついにクリックしてくれたのは、単に「呼び出し側に決定を下す」ことだと気づいたときです。IoCとは、コンポーネントの制御がコンポーネントの作成者からコンポーネントのユーザーに移動することを意味します。そして、その時点で、私は自分よりも賢いソフトウェアで十分なグリップをすでに持っているので、私はすぐにDIアプローチで売られました。
Joker_vD

1
「それだけの関数に引数を渡すだ。それはほとんどすべてでパターンだ」これは、私が聞いたDIの唯一の良い、あるいはcomprehendible説明がある(まあ、ほとんどの人々も、について話していない、それが何であるかではなく、そのメリット)。そして、彼らは「制御の反転」や「疎結合」のような複雑な専門用語を使用しますが、それは本当に単純なアイデアであるかを理解するためにより多くの質問が必要です。
アディソン

7

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

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

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

class Profile {
    private $settings;

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

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

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

これは、Settingsオブジェクトの構築が高価または複雑な場合、またはSettingsが実行時の動作を変更するために複数の具象実装が存在するインターフェイスまたは抽象クラスである場合に役立ちます。

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

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

  1. Profileコンストラクター内でSettingsオブジェクトをインスタンス化します

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

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


5
The example you give is not dependency injection in the classical sense.-それは問題ではありません。オブジェクト指向の人々は脳にオブジェクトを持っていますが、あなたはまだ何か
ロバートハーベイ

1
古典的な意味で」について話すとき、@ RobertHarveyが言うように、あなたは純粋にオブジェクト指向の用語で話します。たとえば関数型プログラミングでは、ある関数を別の関数(高次関数)に注入することが、そのパラダイムの依存性注入の古典的な例です。
デビッドアルノ

3
@RobertHarvey:私は「依存性注入」であまりにも多くの自由を奪っていたと思います。ほとんどの場合、用語を考えて使用する場所は、オブジェクトの構築時、またはセッター注入の場合は直後の「注入」されるオブジェクトのフィールドを参照します。
グレッグブルクハート

@DavidArno:はい、あなたは正しいです。OPには質問にオブジェクト指向のPHPコードスニペットがあるようですので、私はその点についてのみ答え、関数型プログラミングには対処していませんでしたが、PHPでは関数型プログラミングの観点から同じ質問をすることができました。
グレッグブルクハート

7

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

なぜこれを行う必要があります:

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();

そして今でapplicationprofilesetting実際に変化していることについて特に何も知る必要なく、を自由にアクティブ化および非アクティブ化できます。なぜそれが良いのですか?設定を変更する必要がある場合。applicationあなたはできるだけ早くあなたが何かに触れて壊し、すべてを見ることなく、安全に含まれる空間にナットを行くのは自由ですので、これらの変更から遮断壁れます。

これは、動作原理とは別の構成に従います。ここでのDIパターンは単純なものです。できる限り低いレベルで必要なものをすべて構築し、それらを配線して、1回の呼び出しですべての動作を開始します。

その結果、何が何に接続するかを決定するための別の場所と、何に何を言うかを管理するための別の場所ができます。

あなたが時間をかけて維持しなければならない何かでそれを試して、それが役に立たないかどうか見てください。


6

顧客として、あなたが車に何かをするためにメカニックを雇うとき、そのメカニックは最初から車を作り、それからそれを使うことを期待しますか?いいえ、整備士に作業をしてほしい車渡します

ガレージの所有者として、メカニックに車に何かをするように指示するとき、メカニックが自分のドライバー/レンチ/車の部品を作成することを期待していますか?いいえ、メカニックに使用する必要のあるパーツ/ツールを提供します

なぜこれを行うのですか?さて、考えてみてください。あなたはメカニックになるために誰かを雇いたいガレージオーナーです。あなたは彼らにメカニックであることを教えます(=コードを書きます)。

より簡単になるもの:

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

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

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

専門のメカニックを雇って訓練すると、費用がかかり、単純な仕事であるべきことを実行するのにより多くの時間がかかり、多くの責任のいずれかが必要なときはいつでも再訓練する必要がある従業員になります更新されます。

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

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


はい、依存関係の反転は、依存関係をハードコーディングするのではなく、書くのにもう少し努力します。はい、さらに入力しなければならないのは面倒です。

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

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


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

これは、アプリケーションの実行時のパフォーマンスには影響しません。しかし、大規模な影響を与える開発時間(したがってパフォーマンス)の開発


2
マイナーなコメントとして、あなたの質問は依存関係の挿入ではなく依存関係の反転に焦点を当てています。挿入は反転を行う1つの方法ですが、唯一の方法ではありません。」、これは素晴らしい答えです。依存関係の反転は、インジェクションまたはロケーター/グローバルのいずれかを介して実現できます。質問の例は注入に関連しています。したがって、問題依存性注入(および依存性反転)に関するものです。
デビッドアルノ

12
私は、全体の車の事は少しbelabouredと混乱だと思う
ユアン・

4
@Flater、問題の一部は、依存関係の注入、依存関係の反転、および制御の反転の違いが何であるかについて誰も実際に同意していないようです。ただし、確実なことの1つは、メソッドまたはコンストラクターに依存関係を挿入するために「コンテナー」は必要ないことです。純粋な(または貧乏人の)DIは、特に手動の依存性注入について説明しています。コンテナに関連付けられた「魔法」が嫌いなので、個人的に使用する唯一の依存性注入です。
デビッドアルノ

1
@Flater例の問題は、この方法でコードの問題をほぼ確実にモデル化しないことです。したがって、それは不自然で、強制され、回答よりも多くの質問を追加します。そのため、デモンストレーションするよりも誤解を招き、混乱させる可能性が高くなります。
jpmc26

2
@gbjbaanb:私の答えは、ポリモーフィズムを少しでも掘り下げることはありません。継承、インターフェース実装、または型のアップ/ダウンキャストに似たものはありません。あなたは完全に答えを誤解しています。
18年

0

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

依存性注入については、これは間違いなく、OOP設計の最も重要な2つの側面を考えると簡単にわかります。

低結合

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

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

結合を達成したい。強く結びついていることは、一方を変更した場合、もう一方を変更する必要がある可能性が高いことを意味します。一方のバグまたは制限は、他方のバグ/制限を引き起こす可能性があります。等々。

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

高い凝集力

凝集力

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

これはコインの反対側です。1つのコードユニット(1つのメソッド、1つのクラス、1つのパッケージなど)を見ると、そのユニット内のすべてのコードができるだけ少ない責任を持つようにしたいでしょう。

これの基本的な例はMVCパターンです。ドメインモデルをビュー(GUI)とそれらをつなぐコントロールレイヤーから明確に分離します。

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

依存性注入では、DIを実装するクラス(または構成ファイル)に依存関係の作成または追跡を委任します。彼らはいくつかの一般的なインターフェースで作業され、そうでない意味実際の実装が何であるかは考え、持っていない-他のクラスには、正確に何が起こっているかについてはあまり気にしません責任を他のもののために。


-1

あなたがそれらの問題を抱えているとき、彼らは解決が得意な問題を解決するためにテクニックを使うべきです。依存関係の反転と注入も同じです。

依存関係の反転またはインジェクションは、実行時に呼び出されるメソッドの実装をコードが決定できるようにする手法です。これにより、遅延バインディングの利点が最大化されます。この手法は、言語が非インスタンス関数のランタイム置換をサポートしていない場合に必要です。たとえば、Javaには、静的メソッドの呼び出しを別の実装の呼び出しに置き換えるメカニズムがありません。Pythonとは対照的に、関数呼び出しを置き換えるために必要なのは、名前を別の関数にバインドすることだけです(関数を保持する変数を再割り当てします)。

関数の実装を変更したいのはなぜですか?2つの主な理由があります。

  • テスト目的で偽物を使用したい。これにより、実際にデータベースに接続することなく、データベースフェッチに依存するクラスをテストできます。
  • 複数の実装をサポートする必要があります。たとえば、MySQLデータベースとPostgreSQLデータベースの両方をサポートするシステムをセットアップする必要がある場合があります。

コントロールコンテナの反転に注意することもできます。これは、この擬似コードのように見える巨大で絡み合った構築ツリーを回避するためのテクニックです。

thing5 =  new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());

myApp = new MyApp(
    new MyAppDependency1(thing5, thing3),
    new MyAppDependency2(
        new Thing1(),
        new Thing2(new Thing3(thing5, new Thing4(thing5)))
    ),
    ...
    new MyAppDependency15(thing5)
);

クラスを登録してから、構築を行います:

injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);

myApp = injector.create(MyApp); // The injector fills in all the construction parameters.

登録されたクラスがステートレスシングルトンになる可能性がある場合は、最も単純であることに注意してください。

注意事項

依存関係の反転は、デカップリングロジックの頼りになる答えではないことに注意してください。代わりにパラメーター化を使用する機会を探してください。たとえば、次の擬似コードメソッドを考えてみましょう。

myAverageAboveMin()
{
    dbConn = new DbConnection("my connection string");
    dbQuery = dbConn.makeQuery();
    dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
    dbQuery.setParam("min", 5);
    dbQuery.Execute();
    myData = dbQuery.getAll();
    count = 0;
    total = 0;
    foreach (row in myData)
    {
        count++;
        total += row.x;
    }

    return total / count;
}

このメソッドのいくつかの部分に依存関係の反転を使用できます。

class MyQuerier
{
    private _dbConn;

    MyQueries(dbConn) { this._dbConn = dbConn; }

    fetchAboveMin(min)
    {
        dbQuery = this._dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    private _querier;

    Averager(querier) { this._querier = querier; }

    myAverageAboveMin(min)
    {
        myData = this._querier.fetchAboveMin(min);
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

しかし、少なくとも完全ではありません。でステートフルクラスを作成したことに注意してくださいQuerier。現在、本質的にグローバルな接続オブジェクトへの参照を保持しています。これは、プログラムの全体的な状態を理解するのが難しい、さまざまなクラスが互いにどのように連携するのかなどの問題を引き起こします。平均化ロジックをテストする場合、クエリアまたは接続を偽造することを余儀なくされていることにも注意してください。さらにより良いアプローチは、パラメータ化を増やすことです:

class MyQuerier
{
    fetchAboveMin(dbConn, min)
    {
        dbQuery = dbConn.makeQuery();
        dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
        dbQuery.setParam("min", min);
        dbQuery.Execute();
        return dbQuery.getAll();
    }
}


class Averager
{
    averageData(myData)
    {
        count = 0;
        total = 0;
        foreach (row in myData)
        {
            count++;
            total += row.x;
        }

        return total / count;
    }

class StuffDoer
{
    private _querier;
    private _averager;

    StuffDoer(querier, averager)
    {
        this._querier = querier;
        this._averager = averager;
    }

    myAverageAboveMin(dbConn, min)
    {
        myData = this._querier.fetchAboveMin(dbConn, min);
        return this._averager.averageData(myData);
    }
}

接続は、操作全体を担当し、この出力をどう処理するかを知っている、さらに高いレベルで管理されます。

これで、クエリから完全に独立して平均化ロジックをテストできるようになり、さらに多くの状況でそれを使用できるようになりました。オブジェクトMyQuerierAveragerオブジェクトが必要かどうか疑問に思うかもしれませんが、おそらく答えは、ユニットテストを行うつもりがない場合は、データベースに緊密に結合されているため、StuffDoerユニットテストStuffDoerは完全に合理的ではないということでしょう。統合テストでカバーするだけの方が理にかなっているかもしれません。その場合には、我々は細かい作りかもしれないfetchAboveMinし、averageData静的メソッドに。


2
依存性注入は、複雑で複雑な構築ツリーを回避するためのテクニックです...」この主張の後の最初の例は、純粋な、または貧乏人の依存性注入の例です。2番目は、IoCコンテナを使用して、これらの依存関係の注入を「自動化」する例です。両方とも、実行中の依存性注入の例です。
デビッドアルノ

@DavidArnoうん、そうだね。用語を調整しました。
jpmc26

実装を変更する、または少なくとも実装が変更できると仮定してコードを設計する3番目の主な理由があります。未来。そして、それはいくつかのプロジェクトでは優先順位ではないかもしれませんが(例えば、アプリケーションが最初のリリース後に再訪されないことを知っている)、他のプロジェクトでは優先されます。 )。
18年

@Flater依存性注入は、依然として強い結合をもたらします。ロジックを特定のインターフェイスに結び付け、問題のコードがそのインターフェイスが何をするかを知る必要があります。DBから取得した結果を変換するコードを検討してください。DIを使用してそれらを分離する場合、コードはDBフェッチが発生することを認識して呼び出す必要があります。より良い解決策は、変換コードがフェッチが起こっていることすら知らない場合です。これを行う唯一の方法は、フェッチャーを挿入するのではなく、フェッチの結果が呼び出し元によって渡される場合です。
jpmc26

1
@ jpmc26ただし、(コメント)の例では、データベースは依存関係である必要さえありませんでした。もちろん、疎結合の場合は依存関係を回避する方が適切ですが、DIは必要な依存関係の実装に重点を置いています。
フラット
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.