web-dev-qa-db-ja.com

依存性注入を使用する場合の欠点は何ですか?

私はここでDIをパターンとして導入しようとしていますが、リード開発者の1人が知りたいと思っています:何か-もしあれば-欠点依存性注入パターンを使用する方法

注-可能であれば、トピックに関する主観的な議論ではなく、網羅的なリストを探しています。


明確化:依存性注入について話しているパターンこの記事 Martin Fowlerによる) )、not XMLベース(Springなど)、コードベース(Guiceなど)、または「自動ロール」などの特定のフレームワーク。


Edit:さらなる素晴らしい議論/暴言/議論が進行中 / r/programming こちら。

331
Epaga

いくつかのポイント:

  • DIは、責任がより分離されるため、通常はクラスの数を増やすことによって複雑さを増しますが、これは必ずしも有益ではありません
  • コードは、使用する依存性注入フレームワーク(またはより一般的にはDIパターンの実装方法)に(ある程度)結合されます。
  • 型解決を実行するDIコンテナまたはアプローチでは、通常、実行時にわずかなペナルティが発生します(非常にわずかですが、そこにあります)

一般的に、デカップリングの利点により、各タスクは読みやすく、理解しやすくなりますが、より複雑なタスクを調整する複雑さが増します。

201
Håvard S

オブジェクト指向プログラミング、スタイルルール、その他すべてでよくある基本的な問題。実際、非常に一般的なことですが、抽象化を過度に行い、間接性を過度に追加し、一般的に良いテクニックを過度に間違った場所に適用する可能性があります。

適用するすべてのパターンまたはその他の構造は複雑さをもたらします。抽象化とインダイレクションは情報をばらまき、時には無関係な詳細を邪魔にならないように移動させますが、同様に時には何が起こっているのかを正確に理解することを難しくします。適用するすべてのルールには柔軟性がなく、最良のアプローチである可能性のあるオプションが除外されます。

重要なのは、ジョブを実行し、堅牢で読みやすく保守可能なコードを記述することです。あなたはソフトウェア開発者です-アイボリータワービルダーではありません。

関連リンク

http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx

http://www.joelonsoftware.com/articles/fog0000000018.html


おそらく、最も単純な形式の依存性注入(笑ってはいけません)はパラメーターです。依存コードはデータに依存しており、そのデータはパラメーターを渡す手段によって注入されます。

はい、それはばかげており、オブジェクト指向の依存性注入のポイントには対応していませんが、機能プログラマーは(ファーストクラスの関数がある場合)これが必要な唯一の種類の依存性注入であることを教えてくれます。ここでのポイントは、些細な例を取り上げ、潜在的な問題を示すことです。

この単純な従来の関数を見てみましょう-C++構文はここでは重要ではありませんが、どうにかして綴らなければなりません...

void Say_Hello_World ()
{
  std::cout << "Hello World" << std::endl;
}

テキスト「Hello World」を抽出して挿入したい依存関係があります。簡単です...

void Say_Something (const char *p_text)
{
  std::cout << p_text << std::endl;
}

オリジナルよりも柔軟性が低いのはどうしてですか?さて、出力をユニコードにする必要があると判断した場合はどうなりますか。おそらくstd :: coutからstd :: wcoutに切り替えたいと思います。しかし、それは私の文字列がcharではなくwchar_tのものでなければならないことを意味します。すべての呼び出し元を変更する必要があるか、(より合理的に)古い実装は、文字列を変換して新しい実装を呼び出すアダプターに置き換えられます。

これは、元のファイルを保持していれば必要ないメンテナンス作業です。

些細なことに思える場合は、Win32 APIのこの実世界の機能をご覧ください...

http://msdn.Microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx

それは対処すべき12の「依存関係」です。たとえば、画面の解像度が非常に大きくなる場合、64ビットの座標値とCreateWindowExの別のバージョンが必要になる可能性があります。そして、はい、古いバージョンがまだぶら下がっていますが、おそらくそれは舞台裏で新しいバージョンにマップされます...

http://msdn.Microsoft.com/en-us/library/ms632679%28v=vs.85%29.aspx

これらの「依存関係」は、元の開発者だけの問題ではありません-そのインターフェイスを使用するすべての人は、依存関係が何であるか、どのように指定され、何を意味するのかを調べ、アプリケーションで何をすべきかを考えなければなりません。これは、「賢明なデフォルト」という言葉が人生をはるかに単純化できる場所です。

オブジェクト指向の依存性注入は原則として違いはありません。クラスの記述は、ソースコードテキストと開発者の時間の両方でオーバーヘッドになります。また、あるクラスが依存オブジェクトの仕様に従って依存関係を提供するように記述されている場合、依存オブジェクトはそのインターフェースのサポートにロックされます。そのオブジェクトの実装を置き換えます。

これは、依存性注入が悪いと主張するものとして読まれるべきではありません。しかし、良いテクニックは過度に間違った場所に適用される可能性があります。すべての文字列を抽出してパラメーターに変換する必要があるわけではないのと同様に、すべての低レベルの動作を高レベルのオブジェクトから抽出して注入可能な依存関係に変換する必要はありません。

178
Steve314

私自身の最初の反応は次のとおりです。基本的に、どのパターンでも同じ欠点があります。

  • 学ぶには時間がかかります
  • 誤解された場合、それは善よりも害につながる可能性があります
  • 極端に考えると、利益を正当化するよりも多くの作業になる可能性があります
75
Epaga

Inversion of Controlの最大の「欠点」は(完全なDIではありませんが、十分に近い)、アルゴリズムの概要を見るための単一のポイントを削除する傾向があることです。ただし、基本的には、コードを分離した場合に発生します。1か所を見る能力は、密結合の成果物です。

45
kyoryu
41

過去6か月間、Guice(Java DIフレームワーク)を広範囲に使用しています。全体的には(特にテストの観点から)素晴らしいと思いますが、いくつかの欠点もあります。最も注目すべきは:

  • コードを理解するのが難しくなる可能性があります。依存性注入は、非常に...創造的...の方法で使用できます。たとえば、カスタムアノテーションを使用して特定のIOStreams(@ Server1Stream、@ Server2Streamなど)を挿入するコードに出会いました。これは機能しますが、ある程度の優雅さは認めますが、Guiceインジェクションを理解することはコードを理解するための前提条件になります。
  • プロジェクト学習時の学習曲線の高さこれはポイント1に関連しています。依存性注入を使用するプロジェクトの仕組みを理解するには、依存性注入パターンと特定のフレームワークの両方を理解する必要があります。私が現在の仕事を始めたとき、Guiceが舞台裏でやっていたことを、かなりの時間を費やして混乱させていました。
  • コンストラクターは大きくなります。これは、デフォルトのコンストラクターまたはファクトリーで大部分解決できます。
  • エラーは難読化される可能性があります。この最新の例は、2つのフラグ名で衝突があったことです。 Guiceはエラーを静かに飲み込み、フラグの1つが初期化されませんでした。
  • エラーはランタイムにプッシュされます。 Guiceモジュールを誤って設定した場合(循環参照、バインディング不良など)、ほとんどのエラーはコンパイル時に発見されません。代わりに、プログラムが実際に実行されるときにエラーが公開されます。

今、私は不平を言いました。現在のプロジェクトで、そしておそらく次のプロジェクトでもGuiceを(喜んで)使用し続けます。依存性注入は、非常に強力なパターンです。しかし、それは間違いなく混乱を招く可能性があり、ほぼ確実に、選択した依存性注入フレームワークに呪いをかけるでしょう。

また、依存性の注入は使いすぎになる可能性があるという他のポスターにも同意します。

39
Andy Peck

DIのないコードは、 スパゲッティコード に絡まるというよく知られたリスクを実行します-いくつかの症状は、クラスとメソッドが大きすぎ、多すぎて、簡単に変更、分解、リファクタリングできないことです、またはテスト済み。

多くのDIを使用するコードは Ravioli code になります。各小さなクラスは個々のラビオリナゲットのようなものです。1つの小さなことを行い、 単一の責任原則 が順守されます。いいね。しかし、クラスを独力で見ると、システム全体が何をしているのかを見るのは困難です。これは、これらの多くの小さな部品すべてがどのように組み合わされるかに依存するためです。それは小さなものの大きな山のように見えます。

大規模なクラス内の結合コードの大部分のスパゲッティの複雑さを回避することにより、別の種類の複雑さのリスクを負います。単純で小さなクラスが多く、それらの間の相互作用は複雑です。

私はこれが致命的なマイナス面だとは思わない-DIはまだ非常に価値がある。たった1つのことを行う小さなクラスのある程度のラビオリスタイルは、おそらく良いでしょう。過剰であっても、スパゲッティコードとしては悪いとは思いません。しかし、それが行き過ぎている可能性があることを認識することは、それを回避するための最初のステップです。それを回避する方法については、リンクを参照してください。

24
Anthony

自社開発のソリューションを使用している場合、依存関係はコンストラクターで直接確認できます。または多分メソッドパラメータとして、これも見つけにくいことではありません。フレームワークで管理された依存関係は、極端に考えれば、魔法のように見えるようになります。

しかし、あまりにも多くのクラスにあまりにも多くの依存関係があるということは、クラス構造が台無しにされていることの明確な兆候です。そのため、ある意味で、依存性注入(自家製またはフレームワーク管理)は、暗闇に潜んでいる可能性のある明白なデザインの問題を明らかにするのに役立ちます。


2番目のポイントをよりよく説明するために、これからの抜粋を示します 記事元のソース )これは、コンピューターシステムだけでなく、システムを構築する際の根本的な問題だと心から信じています。

大学のキャンパスを設計するとします。設計の一部を学生と教授に委任する必要があります。そうしないと、物理学の建物が物理学の人々にとってうまく機能しません。建築家は、人々が自分ですべてを行うために必要な物理学について十分に理解していません。しかし、すべての部屋のデザインを居住者に委任することはできません。それは、瓦thenの巨大な山を手に入れるからです。

設計全体の一貫性と調和を維持しながら、大規模な階層のすべてのレベルで設計の責任をどのように分散できますか?これは、アレキサンダーが解決しようとしているアーキテクチャ設計の問題ですが、コンピューターシステム開発の根本的な問題でもあります。

DIはこの問題を解決しますか? いいえ。しかし、すべての部屋を設計する責任を居住者に委ねようとしているのかどうかを明確に確認するのに役立ちます。

13
Anurag

私がDIで少し身をよじるのは、注入されたすべてのオブジェクトがインスタンス化するのに安いproduceであるという仮定です副作用なし-OR-依存関係が非常に頻繁に使用されるため、関連するインスタンス化コストを上回る。

これが重要になるのは、消費クラス内で依存関係が頻繁に使用されない場合です。 IExceptionLogHandlerServiceのようなもの。明らかに、このようなサービスは(できれば:))クラス内で呼び出されることはめったにありません-おそらくログに記録する必要がある例外に対してのみです。まだcanonical constructor-injection pattern...

Public Class MyClass
    Private ReadOnly mExLogHandlerService As IExceptionLogHandlerService

    Public Sub New(exLogHandlerService As IExceptionLogHandlerService)
        Me.mExLogHandlerService = exLogHandlerService
    End Sub

     ...
End Class

...このサービスの「ライブ」インスタンスを提供する必要があり、そこに到達するために必要なコスト/副作用を気にしました。おそらくそうではありませんが、この依存関係インスタンスを構築する際に、サービス/データベースのヒット、構成ファイルの検索、または破棄されるまでリソースをロックした場合はどうでしょうか?代わりに、このサービスを必要に応じて、サービスの場所に、または工場で生成されたもの(すべて独自の問題がある)で構築した場合、必要な場合にのみ構築コストがかかります。

現在、オブジェクトの構築はis安く、does n't副作用が発生します。そして、それはニースの概念ですが、常にそうとは限りません。ただし、典型的なコンストラクター注入を使用する場合、基本的にはそうであることが要求されます。依存関係の実装を作成するときは、DIを念頭に置いて設計する必要があります。他の場所で利益を得るためにオブジェクト構築をより高価なものにしたかもしれませんが、この実装が注入される場合、その設計を再検討することを余儀なくされるでしょう。

ところで、特定の手法では、注入された依存関係の遅延読み込みを許可することにより、この正確な問題を軽減できます。クラスにLazy<IService>インスタンスを依存関係として提供します。これにより、依存オブジェクトのコンストラクタが変更され、オブジェクト構築費用などの実装の詳細がさらに認識されるようになりますが、これも間違いなく望ましくありません。

12
ckittel

実際にデカップリングせずに依存性注入を実装するだけでコードをデカップリングしたという錯覚。これがDIの最も危険なことだと思います。

12
Joey Guerra

これはちょっとした選択です。しかし、依存性注入の欠点の1つは、開発ツールがコードを推論してナビゲートするのが少し難しくなることです。

具体的には、コード内のメソッド呼び出しでControlキーを押しながらクリックまたはCommandキーを押しながらクリックすると、具体的な実装ではなく、インターフェイス上のメソッド宣言に移動します。

これは実際には疎結合コード(インターフェイスによって設計されたコード)の欠点であり、依存関係の注入を使用しない場合(つまり、単に工場を使用する場合でも)に適用されます。しかし、依存性注入の出現は、大衆への疎結合コードを本当に奨励したものですので、私はそれを言及すると思いました。

また、疎結合コードの利点はこれをはるかに上回っているため、私はそれをnitpickと呼びます。依存関係の注入を導入しようとすると、これがプッシュバックの一種であることを知るのに十分な時間をかけてきましたが。

実際、依存関係の注入について見つけることができるすべての「欠点」については、それをはるかに上回る多くの利点が見つかると思います。

11
Jack Leow

コンストラクタベース依存性注入(魔法の「フレームワーク」の助けなし)は、OOコードを構造化するためのクリーンで有益な方法です。私が見た最高のコードベースで、マーティン・ファウラーの他の元同僚と何年も費やして、この方法で書かれたほとんどの良いクラスが単一のdoSomethingメソッドを持つことに気付き始めました。

主要な欠点は、関数型プログラミングの利点を得るためにクラスとしてクロージャーを書くという非常に手間のかかるOO方法であることに気付くと、OOコードはすぐに蒸発します。

10
sanityinc

コンストラクターインジェクションは大きなbigいコンストラクターにつながる可能性があることを発見しました(そして、コードベース全体で使用しています-おそらくオブジェクトが細かすぎますか?)。また、コンストラクター注入では、恐ろしい循環依存関係が発生することがあります(これは非常にまれですが)ので、より複雑なシステムでいくつかのラウンドの依存関係注入を行う準備ができた状態のライフサイクルが必要になることがあります。

ただし、オブジェクトが構築されると、ユニットテスト環境にあるか、何らかのIOCコンテナにロードされているかどうか、疑いなくオブジェクトの状態がわかるので、セッターインジェクションよりもコンストラクタインジェクションを優先します。ラウンドアバウトのように、セッター注入の主な欠点は私が感じていることだと言っています。

(補足として、私はトピック全体を非常に「宗教的」であると思いますが、あなたの走行距離は開発チームの技術的な熱意のレベルによって異なります!)

9
James B

IOCコンテナなしでDIを使用している場合、最大の欠点は、コードに実際にある依存関係の数と、すべてが実際に緊密に結合されていることをすばやく確認できることです。 (「しかし、私はそれが良いデザインだと思った!」)自然な進歩は、学習と実装に少し時間がかかるIOCコンテナーに向かって進むことです(WPF学習ほど悪くはありません)曲線ですが、無料ではありません)。最後の欠点は、一部の開発者が善良な単体テストに正直に書き始め、それを理解するのに時間がかかることです。以前に半日で何かを開発できた開発者は、すべての依存関係をモックする方法を見つけようとして突然2日間を費やします。

マーク・シーマンの答えと同様に、一番下の行は、コードの断片を一緒にハッキングして、それをドア/生産に放り込むよりも、より良い開発者になるために時間を費やすということです。あなたのビジネスはどちらを好むでしょうか?あなただけがそれに答えることができます。

8
Mike Post

DIはテクニックまたはパターンであり、フレームワークとは関係ありません。依存関係は手動で接続できます。 DIは、SR(単一責任)およびSoC(懸念の分離)に役立ちます。 DIはより良い設計につながります。私の視点と経験から欠点はありません。他のパターンと同様に、間違ったり誤用したりする可能性があります(ただし、DIの場合は非常に困難です)。

フレームワークを使用して、レガシーアプリケーションにDIを原則として導入する場合、できる最大の間違いは、それをサービスロケーターとして誤用することです。 DI + Framework自体は素晴らしく、見たところどこでも物事が良くなりました!組織の観点から、すべての新しいプロセス、手法、パターンには共通の問題があります...:

  • チームを訓練する必要があります
  • アプリケーションを変更する必要があります(リスクを含む)

一般的に、時間とお金を投資するである必要があります。それ以外に、マイナス面はありません、本当に!

5
Robert

コードの読みやすさ。依存関係はXMLファイルに隠されているため、コードフローを簡単に把握することはできません。

3
Rahul

静的型付けを回避するための手法を絶えず使用している場合、静的型付け言語の想定される利点は大幅に減少するようです。面接したばかりの大きなJavaショップは、ビルドの依存関係を静的コード分析でマッピングしていました。

2
Chris D

2つのこと:

  • 構成が有効であることを確認するには、追加のツールサポートが必要です。

たとえば、IntelliJ(商用版)は、Spring構成の有効性のチェックをサポートしており、構成のタイプ違反などのエラーにフラグを立てます。そのようなツールのサポートがないと、テストを実行する前に構成が有効かどうかを確認できません。

これが、「Scalaコミュニティに知られているように」「ケーキ」パターンが良いアイデアである理由の1つです。コンポーネント間の配線は型チェッカーでチェックできます。アノテーションやXMLを使用するメリットはありません。

  • プログラムのグローバルな静的分析が非常に困難になります。

SpringやGuiceのようなフレームワークは、コンテナによって作成されたオブジェクトグラフがどのように見えるかを静的に決定することを困難にします。コンテナの起動時にオブジェクトグラフを作成しますが、作成されるオブジェクトグラフを記述する有用なAPIは提供しません。

2
Martin Ellis

IoCコンテナは適切な方法で依存関係を解決する必要があるため、アプリの起動時間を長くすることができ、時には複数の反復を行う必要があります。

1
Roman