web-dev-qa-db-ja.com

IOCコンテナが壊れるOOP原則

IOCコンテナの目的は何ですか?それを組み合わせた理由は、次のように簡略化できます:

OOP/SOLID開発の原則を使用すると、Dependency Injectionが乱雑になります。最下位の複数レベルの依存関係を管理するトップレベルのエントリポイントがあり、依存関係を構築を通じて再帰的に渡すか、必要に応じて依存関係を構築するファクトリ/ビルダーパターンとインターフェイスにコードがいくらか重複しています。

これを実行するOOP/SOLIDの方法はありません[〜#〜]および[〜#〜]非常にきれいなコードがあります。

その前のステートメントが真の場合、IOC Containersはそれをどのように行うのですか?私の知る限り、手動のDIでは実行できない未知の手法を採用していません。説明は、IOCコンテナが静的オブジェクトのプライベートアクセサを使用してOOP/SOLIDの原則を破るということです。

IOCコンテナは、舞台裏で以下の原則を破りますか?私はよく理解しているので、これは本当の問題ですが、他の誰かがよりよく理解していると感じています:

  1. スコープ制御。スコープは、コード設計でほぼすべての決定を下す理由です。ブロック、ローカル、モジュール、静的/グローバル。スコープは非常に明示的である必要があり、ブロックレベルではできるだけ多く、Staticではできるだけ少なくする必要があります。宣言、インスタンス化、およびライフサイクルの終了が表示されます。言語とGCは、明示的に説明する限り、スコープの管理に信頼されます。私の調査では、IOCコンテナがほとんどまたはすべての依存関係を静的として設定し、バックグラウンドでいくつかのAOP実装を介してそれらを制御していることを発見しました。したがって、何も透明ではありません。
  2. カプセル化。カプセル化の目的は何ですか?なぜ私たちはプライベートメンバーを維持する必要があるのですか?実際の理由により、APIの実装者は状態(所有者クラスによって管理される必要がある)を変更することによって実装を壊すことができないようになっています。ただし、セキュリティ上の理由から、加盟国を追い越して検証とクラス制御をバイパスするインジェクションは発生しません。したがって、プライベートメンバーへの外部アクセスを許可するためにコンパイル時間の前に何らかの方法でコードを挿入するもの(モッキングフレームワークまたはIOCフレームワーク))は非常に巨大です。
  3. 単一の責任の原則。表面的には、IOCコンテナは物事をよりきれいにするように見えます。しかし、ヘルパーフレームワークなしで同じことを実現する方法を想像してみてください。十数個程度の依存関係が渡されるコンストラクターが渡されるでしょう。それはIOC Containersでカバーするという意味ではありません。これは良いことです!これは、コードをリファクタリングしてSRPをフォローする兆候です。
  4. オープン/クローズ。 SRPがクラスのみではないのと同じように(メソッドは言うまでもなく、SRPを単一の責任範囲に適用します)。 Open/Closedは、クラスのコードを変更しないための単なる高レベルの理論ではありません。これは、プログラムの構成を理解し、何が変更され、何が拡張されるかを制御する習慣です。 IOCコンテナは、次の理由により、クラスの全体的な動作方法を部分的に変更できます。

    • a。メインコードは依存関係の切り替えを決定するのではなく、フレームワークの構成です。

    • b。スコープは、呼び出し元のメンバーによって制御されていないときに変更される可能性があり、静的フレームワークによって外部で決定されます。

したがって、クラスの構成は実際には閉じていません。サードパーティツールの構成に基づいて、クラス自体が変更されます。

これが質問である理由は、私が必ずしもすべてのIOCコンテナのマスターではないためです。そしてIOCコンテナのアイデアは素晴らしいですが、不十分な実装をカバーするファサードにすぎません。しかし、外部スコープ制御、プライベートメンバーアクセス、および遅延読み込みを実現するには、多くの重要な問題が続いている必要があります。AOPは素晴らしいですが、その方法はIOCコンテナを通じて達成されたも疑わしいです。

C#と.NET GCを信頼して、私が期待することを実行できます。しかし、私がコンパイルしたコードを変更して、手動では実行できない問題の回避策を実行するサードパーティツールに、同じ信頼を置くことはできません。

例:Entity Frameworkやその他のORMは、強く型付けされたオブジェクトを作成し、それらをデータベースエンティティにマップし、CRUDを実行するための定型的な基本機能を提供します。誰でも独自のORMを構築し、手動でOOP/SOLID原則を継続できます。しかし、これらのフレームワークは私たちを助けるので、毎回車輪を再発明する必要はありません。一方、IOCコンテナは、OOP/SOLIDの原則を意図的に回避してカバーするのに役立ちます。

51
Suamere

私はあなたのポイントを数値で説明しますが、最初に、あなたが非常に注意すべき何かがあります:コンシューマがライブラリを使用する方法とライブラリが実装される方法を混同しないでください。これの良い例は、Entity Framework(あなた自身が良いライブラリとして引用している)とASP.NETのMVCです。これらは両方とも、たとえばリフレクションなどの内部でひどい多くのことを行いますが、日常のコードにそれを広めた場合、これは優れたデザインとは絶対に見なされません。

これらのライブラリは完全にnotは、それらがどのように機能するか、または舞台裏で何をしているのかについて「透明」です。ただし、consumersで優れたプログラミング原則をサポートしているため、これは問題ではありません。したがって、このようなライブラリについて話すときはいつでも、ライブラリの利用者として、その実装や保守について心配するのはあなたの仕事ではないことを覚えておいてください。あなたはそれがライブラリを使用するあなたが書いたコードをどのように助けるか妨げるかについて心配するだけです。これらの概念を混同しないでください!

だから、ポイントで通過するには:

  1. すぐに、私は上記の例であるとしか想定できないことに到達します。 IOCコンテナはほとんどの依存関係を静的としてセットアップします。まあ、おそらくそれらが機能する実装の詳細には静的ストレージが含まれます(ただし、Ninjectのようなオブジェクトのインスタンスを持つ傾向があることを考えるとIKernelをコアストレージとして使用しているとは思いませんが、これは問題ではありません。

    実際、IoCコンテナは、貧乏人の依存性注入と同じようにスコープが明示されています。 (IoCコンテナーを貧乏人のDIと比較し続けるのは、DIがないと比較すると不公平で混乱を招くためです。また、明確に言うと、「貧乏人のDI」を軽蔑的な表現として使用していません。)

    貧乏人のDIでは、依存関係を手動でインスタンス化し、それを必要とするクラスに注入します。依存関係を構築する時点で、それをどうするかを選択します。ローカルスコープの変数、クラス変数、静的変数に保存します。何も保存しないでください。同じインスタンスを多数のクラスに渡すことも、クラスごとに新しいインスタンスを作成することもできます。なんでも。重要なのは、どちらが起こっているかを確認するには、依存関係が作成されたポイント(おそらくアプリケーションルートの近く)に目を向けることです。

    では、IoCコンテナについてはどうでしょうか?まあ、あなたはまったく同じことをします!繰り返しになりますが、Ninjectの用語で説明すると、バインディングが設定されている場所を調べて、InTransientScopeInSingletonScopeなどと表示されているかどうかを確認します。何かはっきりしている場合、あるオブジェクトに何が起こるかを追跡するためにメソッドを調べる必要がないので、そのスコープを宣言するコードがあるので、これはより明確です(オブジェクトはブロックにスコープされているかもしれませんが、その中で複数回使用されています)ブロックまたは1回だけ)。したがって、スコープを指示するために、生の言語機能ではなくIoCコンテナの機能を使用する必要があるという考えに反発する可能性がありますが、IoCライブラリを信頼している限り、そうするべきです!-実際には不利ではありません。 。

  2. 私はまだあなたがここで何を話しているのか本当にわかりません。 IoCコンテナは、内部実装の一部としてプライベートプロパティを調べますか?彼らがなぜそうするのかはわかりませんが、彼らがそうしたとしても、使用しているライブラリがどのように実装されているかは問題ではありません。

    または、おそらく、それらはプライベートセッターに注入するような機能を公開しますか?正直なところ、私はこれに遭遇したことがなく、これが本当に一般的な機能であるかどうかは疑問です。しかし、それがあっても、悪用される可能性のあるツールの単純なケースです。 IoCコンテナーがなくても、プライベートプロパティにアクセスして変更するためのReflectionコードはほんの数行です。これはほとんどすべきではないことですが、.NETが機能を公開するのに悪いわけではありません。誰かがツールを非常にはっきりと乱用した場合、それはツールのせいではなく、その人の責任です。

  3. ここでの最終的なポイントは2に似ています。ほとんどの場合、IoCコンテナーはコンストラクターを使用します!セッターインジェクションは、 、特定の理由により、コンストラクター注入は使用できません。渡される依存関係の数をカバーするためにセッターインジェクションを常に使用する人は誰でも、ツールを大いに禁じています。それはツールのせいではなく、彼らのせいです。

    さて、これが無邪気に犯しやすい本当に簡単な間違いであり、IoCコンテナーが奨励するものであるなら、さて、多分あなたはポイントを持っているでしょう。それは私のクラスのすべてのメンバーを公開して、他の人が禁止すべきことを変更したときに他の人を非難するようなものですよね?しかし、SRPの違反を隠蔽するためにセッター注入を使用する人は誰でも、基本的な設計原則を故意に無視するか完全に無知です。 IoCコンテナでこれを非難するのは不合理です。

    それは特にtrueです。これは、貧しい人のDIでも簡単にできることだからです。

    var myObject 
       = new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
    

    つまり、この心配は、IoCコンテナが使用されているかどうかに完全に直交するようです。

  4. クラスの連携方法を変更しても、OCPに違反することはありません。そうであれば、依存関係の逆転はすべてOCPの違反を助長することになります。これが事実である場合、それらは両方とも同じSOLID頭字語ではありません!

    さらに、ポイントa)もb)も、OCPとは何の関係もありません。 OCPに関してこれらにどのように答えるかさえ知りません。

    私が推測できる唯一のことは、OCPは実行時に動作が変更されないこと、またはコードのどこで依存関係のライフサイクルが制御されているかと関係があるということです。そうではありません。 OCPは、要件が追加または変更されたときに既存のコードを変更する必要がないことについてです。それは、コードコードに関するすべてであり、すでに作成したコードをどのように接着するかではありません(もちろん、疎結合は達成するための重要な部分ですが) OCP)。

そしてあなたが言う最後の1つ:

しかし、私がコンパイルしたコードを変更して、手動では実行できない問題の回避策を実行するサードパーティツールに、同じ信頼を置くことはできません。

はい、できます。膨大な数のプロジェクトに依存しているこれらのツールが、他のサードパーティのライブラリよりもバグが多く、予期しない動作が発生しやすいと考える理由はまったくありません。

補遺

私はあなたのイントロパラグラフがいくつかのアドレッシングも使用できることに気づきました。 IoCコンテナーは、依存関係グラフを作成するための面倒で重複しやすいコードを回避するために、「聞いたこともないような秘密の手法を採用していない」と皮肉に言います。そして、あなたの言うとおり、彼らがしていることは、プログラマーがいつもしているのと同じ基本的なテクニックで実際にこれらのことに取り組んでいることです。

架空のシナリオについて説明します。プログラマーとして大きなアプリケーションを組み立て、オブジェクトグラフを作成しているエントリポイントで、かなり厄介なコードがあることに気づきます。何度も何度も使用されるかなりの数のクラスがあり、それらの1つをビルドするたびに、その下の依存関係のチェーン全体を再度ビルドする必要があります。さらに、依存関係のライフサイクルを宣言または制御するための表現力のある方法がないことがわかります。ただし、それぞれのカスタムコードを使用する場合を除きます。コードは構造化されておらず、繰り返しでいっぱいです。これは、イントロの段落で話している乱雑さです。

そのため、最初に少しリファクタリングを開始します-いくつかの繰り返しコードは十分に構造化されており、ヘルパーメソッドなどに引き出します。しかし、あなたは考え始めます-これはおそらく一般的なの意味で取り組むことができる問題であり、この特定のプロジェクトに固有ではありませんが、将来のすべてのプロジェクトであなたを助けることができますか?

だから、座って考えて、依存関係を解決できるクラスがあるべきだと決めました。そして、あなたはそれが必要とするどんなパブリックメソッドをスケッチします:

void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();

Bindは、「タイプinterfaceTypeのコンストラクター引数がある場所で、concreteTypeのインスタンスを渡す」と言います。追加のsingletonパラメータは、毎回同じconcreteTypeのインスタンスを使用するか、常に新しいインスタンスを作成するかを示します。

Resolveは、以前にバインドされたすべての型の引数を引数として持つコンストラクタを使用して、Tを構築しようとします。また、依存関係を完全に解決するために、それ自体を再帰的に呼び出すこともできます。すべてがバインドされていないためにインスタンスを解決できない場合は、例外がスローされます。

これを自分で実装してみると、少し反省する必要があり、singletonがtrueであるバインディングのキャッシングが必要ですが、極端なことや恐ろしいことは何もありません。そして、完了したら-voila、あなたはあなた自身のIoCコンテナーのコアを持っています!本当に怖いですか?これとNinject、StructureMap、Castle Windsor、またはあなたが好むものとの唯一の本当の違いは、これらの基本的なバージョンでは不十分な(多くの!)ユースケースをカバーするためのより多くの機能があることです。しかし、その中心には、IoCコンテナーの本質があります。

35
Ben Aaronson

IOCコンテナはこれらの原則の多くを壊しませんか?

理論的には?いいえ。IoCコンテナは単なるツールです。単一の責任を持つオブジェクト、十分に分離されたインターフェース、作成後に動作を変更できないオブジェクトなどを持つことができます。

実際には? 絶対に。

つまり、IoCコンテナを使用していると言うプログラマーは少なくない特に構成を使用してシステムの動作を変更できるようにする-オープン/クローズの原則に違反していると聞いたことがあります。彼らの仕事の90%がSpring構成を修正することだったので、XMLでのコーディングについてジョークが作られていました。そして、依存関係を非表示にすること(確かに、すべてのIoCコンテナがそうであるわけではない)が、高度に結合された非常に壊れやすいコードを作成するための迅速かつ簡単な方法であることは間違いありません。

別の方法で見てみましょう。単一の基本的な依存関係を持つ非常に基本的なクラスを考えてみましょう。これは、Action、つまりパラメーターを受け取らずvoidを返すデリゲートまたはファンクターに依存しています。このクラスの責任は何ですか?わかりません。その依存関係にCleanUpActionのような明確な名前を付けることができます。 IoCが登場するとすぐにanythingになります。誰でも設定に入り、ソフトウェアを変更してほとんど任意のことを行うことができます。

Actionをコンストラクタに渡すのとどう違うのですか?」あなたが尋ねる。ソフトウェアがデプロイされると(または実行時に!)、コンストラクターに渡される内容を変更できないため、状況は異なります。ユニットテスト(またはコンシューマーのユニットテスト)では、デリゲートがコンストラクターに渡されるシナリオをカバーしているため、これは異なります。

「しかし、それは偽の例です!私のものは動作の変更を許可していません!」あなたは叫ぶ。申し訳ありませんが、そうです。注入しているインターフェイスがjust dataでない限り。そして、あなたのインターフェースは単なるデータではありません。それが単なるデータであるなら、あなたは単純なオブジェクトを構築してそれを使用することになるからです。インジェクションポイントに仮想メソッドがあるとすぐに、IoCを使用したクラスの契約は「私はできるanything "」になります。

「スクリプトを使用して物事を進めるのとどう違うのですか?それは完全に問題ありません。」あなたの一部は尋ねます。 違いはありません。プログラム設計の観点からは、違いはありません。プログラムを呼び出して、任意のコードを実行しています。そして確かに、それは長い間ゲームで行われてきました。それはたくさんのシステム管理ツールで行われます。 OOPですか?あんまり。

彼らの現在の至る所に言い訳はありません。 IoCコンテナーには1つの確固たる目的があります。依存関係を非常に多く組み合わせたときに依存関係をまとめたり、急激にシフトして依存関係を明示的にすることは非現実的です。そのため、実際にはそのシナリオに当てはまる企業はほとんどありません。

27
Telastyn

IoCコンテナーの使用は、必ずしもOOP/SOLIDの原則に違反しますか? いいえ

IoCコンテナーをOOP/SOLIDの原則に違反するような方法で使用できますか? はい

IoCコンテナーは、アプリケーションの依存関係を管理するための単なるフレームワークです。

レイジーインジェクション、プロパティセッター、および使用の必要性をまだ感じていない他のいくつかの機能について説明しましたが、これは同意するものであり、最適なデザインとは言えません。ただし、これらの特定の機能の使用は、IoCコンテナーを実装するテクノロジーの制限によるものです。

たとえば、ASP.NET Webフォームでは、Pageをインスタンス化することができないため、プロパティインジェクションを使用する必要があります。 Webフォームは厳密なOOPパラダイムを念頭に置いて設計されたことがないため、これは理解できます。

一方、ASP.NET MVCでは、非常にOOP/SOLID対応のコンストラクターインジェクションを使用してIoCコンテナーを使用することが完全に可能です。

このシナリオ(コンストラクターインジェクションを使用)では、SOLIDの原則を次の理由で促進します。

  • 単一の責任の原則-多くの小さなクラスがあると、これらのクラスを非常に具体的にすることができ、存在する理由が1つあります。 IoCコンテナーを使用すると、それらの整理がはるかに簡単になります。

  • オープン/クローズ-これは、IoCコンテナーの使用が本当に優れているところです。さまざまな依存関係を注入することにより、クラスの機能を拡張できます。注入する依存関係があると、そのクラスの動作を変更できます。良い例は、キャッシングまたはロギングを追加するようにデコレーターを作成することにより、注入するクラスを変更することです。適切な抽象化レベルで記述されている場合は、依存関係の実装を完全に変更できます。

  • リスコフ代替原則-あまり関連性がない

  • インターフェースの分離の原則-必要に応じて、複数のインターフェースを1つの具体的な時間に割り当てることができます。

  • 依存関係の反転-IoCコンテナを使用すると、依存関係の反転を簡単に実行できます。

一連の依存関係を定義し、関係とスコープを宣言し、bing bang boom:コードやI.L.を変更するアドオンを魔法のように持っています。ある時点で物事を機能させます。それは明示的でも透過的でもありません。

明示的かつ透過的です。依存関係を結び付ける方法とオブジェクトの有効期間を管理する方法をIoCコンテナに伝える必要があります。これは、慣例、構成ファイル、またはシステムのメインアプリケーションで記述されたコードのいずれかです。

IOCコンテナを使用してコードを書き込んだり読み込んだりすると、少しエレガントでないコードが削除されることに同意します。

これが、システムを管理しやすくするためのOOP/SOLIDの根本的な理由です。 IoCコンテナーを使用することは、その目標に沿ったものです。

そのクラスの作成者は、「IOCコンテナを使用しなかった場合、コンストラクタには12個のパラメータが含まれることになります!!!それは恐ろしいことです!」

12の依存関係を持つクラスは、どのコンテキストで使用しているかに関係なく、おそらくSRP違反です。

14
Matthew

IOCコンテナは、多くのOOP/SOLID原則を破り、コーダーが破ることを許可しないのですか?

C#のstaticキーワードを使用すると、開発者は多くのOOP/SOLID原則を破ることができますが、それでも適切な状況では有効なツールです。

IoCコンテナーは、適切に構造化されたコードを可能にし、開発者の認知的負担を軽減します。 IoCコンテナを使用すると、SOLIDの原則をはるかに簡単に実行できます。誤用される可能性がありますが、誤用される可能性のあるツールの使用を除外すると、プログラミングをあきらめる必要があります完全に。

したがって、この暴言は不十分に書かれたコードについていくつかの有効な点を提起しますが、それは正確には問題ではなく、正確には役に立ちません。

5
Stephen

あなたの質問には複数の間違いや誤解が見られます。最初の大きな問題は、プロパティ/フィールドインジェクション、コンストラクターインジェクション、サービスロケーターの区別が不足していることです。物事を行うための正しい方法はどれであるかについて、多くの議論(例:フレームウォーズ)がありました。あなたの質問では、プロパティの注入のみを想定し、コンストラクタの注入を無視しているようです。

2つ目は、IoCコンテナーがコードの設計に何らかの影響を与えると想定していることです。 IoCを使用している場合、コードのデザインを変更する必要はありません。少なくとも変更する必要はありません。多くのコンストラクターを持つクラスの問題は、IoCがコードの実際の問題(おそらくSRPが壊れているクラス)を隠すケースであり、非表示の依存関係は、プロパティインジェクションとサービスロケーターの使用を妨げる理由の1つです。

開閉原理のどこに問題があるのか​​わからないのでコメントしません。しかし、これに大きな影響を与えるSOLIDの一部が欠けています:依存関係の逆転の原理。 DIPに従うと、依存関係を反転すると抽象化が導入され、クラスのインスタンスが作成されるときに実装を解決する必要があることがわかります。このアプローチを使用すると、多くのクラスが作成され、適切に機能するために、構築時に複数のインターフェースを実現および提供する必要があります。次に、これらの依存関係を手動で提供する場合、追加の作業をたくさん行う必要があります。 IoCコンテナを使用すると、この作業が簡単になり、プログラムを再コンパイルすることなく変更することもできます。あなたの質問では、おそらくオープン/クローズの原則と依存関係の逆転の原則を混同しているでしょう。

だから、あなたの質問への答えはノーです。 IoCコンテナは、OOP設計原則に違反しません。まったく逆に、SOLID原則(特に、依存性反転の原則))によって引き起こされる問題を解決します。

3
Euphoric