web-dev-qa-db-ja.com

依存性注入はカプセル化を犠牲にする必要がありますか?

私が正しく理解している場合、依存性注入の一般的なメカニズムは、クラスのコンストラクターまたはクラスのパブリックプロパティ(メンバー)を介して注入することです。

これは、注入される依存関係を明らかにし、OOPカプセル化の原則に違反します。

このトレードオフを特定するのは正しいですか?この問題にどのように対処しますか?

以下の自分の質問に対する私の回答も参照してください。

128
urig

この問題には、興味深いと思われる別の見方があります。

IoC /依存性注入を使用する場合、OOPの概念を使用していません。確かに、OO言語を「ホスト」として使用していますが、 IoCの背後にあるアイデアは、オブジェクト指向ではなく、コンポーネント指向のソフトウェアエンジニアリングに由来しています。

コンポーネントソフトウェアは、依存関係の管理に関するものです。一般的な使用例は、.NETのアセンブリメカニズムです。各アセンブリは、参照するアセンブリのリストを公開します。これにより、実行中のアプリケーションに必要な部分をまとめて(そして検証して)より簡単になります。

IoCを介してOOプログラムに同様の手法を適用することで、プログラムの構成と保守を容易にすることを目指しています。依存関係の公開(コンストラクターのパラメーターなど)は、これの重要な部分です。カプセル化はコンポーネント/サービス指向の世界と同様に、詳細が漏れる「実装タイプ」はありません。

残念ながら、現在のところ私たちの言語は、きめの細かいオブジェクト指向の概念を、よりきめの細かいコンポーネント指向の概念から分離していません。そのため、これはあなたが心に留めておかなければならない違いです:)

62

これは良い質問です。しかし、ある時点で、オブジェクトがその依存関係を満たす必要がある場合は、最も純粋な形式でのカプセル化に違反する必要があります。依存関係の一部のプロバイダーmustは、問題のオブジェクトがFooを必要とすることと、Fooをオブジェクトに提供する方法が必要であることの両方を知っています。

古典的には、後者のケースは、あなたが言うように、コンストラクター引数またはセッターメソッドを介して処理されます。ただし、これは必ずしも正しいとは限りません。たとえば、JavaのSpring DIフレームワークの最新バージョンでは、プライベートフィールドに注釈を付けることができます(例:@Autowired)と依存関係は、クラスのパブリックメソッド/コンストラクターを介して依存関係を公開する必要なく、リフレクションを介して設定されます。これは、あなたが探していた種類のソリューションかもしれません。

そうは言っても、コンストラクターの注入がそれほど問題になるとは思いません。オブジェクトは、構築後は完全に有効である必要があると常に感じていました。つまり、オブジェクトが役割を実行するために必要なもの(つまり、有効な状態にあるもの)は、とにかくコンストラクタを通じて提供する必要があります。コラボレーターの動作を必要とするオブジェクトがある場合、コンストラクターがこの要件を公にアドバタイズし、クラスの新しいインスタンスが作成されたときに確実に満たされるようにすることは私には問題ないようです。

理想的には、オブジェクトを処理するときは、とにかくインターフェースを介してオブジェクトとやり取りします。これを行う(およびDIを介して依存関係を配線する)ほど、実際にコンストラクターを扱う必要が少なくなります。理想的な状況では、コードはクラスの具体的なインスタンスを処理したり作成したりすることはありません。したがって、DIを通じてIFooが与えられるだけで、FooImplのコンストラクターがその仕事をする必要があることを示していることを心配することなく、実際にFooImpl存在。この観点から、カプセル化は完璧です。

これは当然の意見ですが、私の心にDIは必ずしもカプセル化に違反しているわけではなく、実際に内部の必要なすべての知識を1か所に集中させることでそれを助けることができます。これはそれ自体が良いことであるだけでなく、この場所が独自のコードベースの外にあるので、クラスの依存関係について記述する必要のあるコードはありません。

29
Andrzej Doyle

これは、注入される依存関係を明らかにし、OOPカプセル化の原則に違反します。

まあ、率直に言って、すべてがカプセル化に違反しています。 :)それはよく扱われなければならない一種の優しい原則です。

それで、カプセル化に違反するものは何ですか?

継承を行います。

「継承はサブクラスをその親の実装の詳細に公開するため、しばしば「継承はカプセル化を壊す」と言われています。」 (ギャングオブフォー1995:19)

アスペクト指向プログラミングdoes。たとえば、onMethodCall()コールバックを登録すると、通常のメソッド評価にコードを挿入し、奇妙な副作用などを追加する絶好の機会が得られます。

C++でのフレンド宣言does

Rubydoesでのクラス拡張。文字列クラスが完全に定義された後のどこかに文字列メソッドを再定義するだけです。

まあ、たくさんのものがします

カプセル化は良い重要な原則です。しかし、それだけではありません。

switch (principle)
{
      case encapsulation:
           if (there_is_a_reason)
      break!
}
17

はい、DIはカプセル化(「情報隠蔽」とも呼ばれます)に違反しています。

しかし、本当の問題は、開発者がKISS(Keep It Short and Simple)およびYAGNI(You Ai n't Gonna Need It))の原則に違反する言い訳としてそれを使用するときに発生します。

個人的には、私はシンプルで効果的なソリューションを好みます。私は主に「新しい」演算子を使用して、必要なときにいつでもどこでもステートフルな依存関係をインスタンス化します。シンプルで、よくカプセル化されており、理解しやすく、テストも簡単です。では、なぜでしょうか。

13
Rogério

ジェフ・スターナルが質問へのコメントで指摘したように、答えはあなたがカプセル化をどのように定義するかに完全に依存しています。

カプセル化の意味については、2つの主要な陣営があるようです。

  1. オブジェクトに関連するものはすべて、オブジェクトのメソッドです。したがって、Fileオブジェクトには、SavePrintDisplayModifyTextなどのメソッドを含めることができます。
  2. オブジェクトはそれ自体の小さな世界であり、外部の動作に依存しません。

これらの2つの定義は互いに直接矛盾しています。 Fileオブジェクトがそれ自体を印刷できる場合、それはプリンターの動作に大きく依存します。一方、それだけで印刷できるもの(IFilePrinterまたはそのようなインターフェイス)がknowsの場合、Fileオブジェクトには印刷について何でも知っているので、それを使って作業することで、オブジェクトへの依存性が減ります。

したがって、最初の定義を使用すると、依存性注入によりカプセル化が解除されます。しかし、率直に言って、私が最初の定義が好きかどうかはわかりません-それは明らかにスケーリングしません(そうした場合、MS Wordは1つの大きなクラスになります)。

一方、カプセル化の2番目の定義を使用している場合、依存関係の注入はほぼ必須です。

5
kyoryu

優れた依存性注入コンテナー/システムは、コンストラクター注入を可能にします。依存オブジェクトはカプセル化されるため、公開する必要はまったくありません。さらに、DPシステムを使用することで、オブジェクトの構築方法の詳細を「知る」ことさえできません。この場合、カプセル化はさらに多くなります。コードのほとんどすべてが、カプセル化されたオブジェクトの知識から保護されるだけでなく、オブジェクトの構築にも関与しないためです。

ここで、作成したオブジェクトが独自のカプセル化されたオブジェクトを作成する場合(おそらくコンストラクター内)と比較していると仮定します。 DPについての私の理解は、この責任をオブジェクトから取り除き、それを他の誰かに与えたいというものです。そのために、この場合のDPコンテナーである「他の誰か」は、カプセル化に「違反」する親密な知識を持っています。利点は、オブジェクト自体からその知識を引き出すことです。誰かが持っている必要があります。アプリケーションの残りの部分はしません。

私はそれをこのように考えます:ディペンデンシーインジェクションコンテナー/システムはカプセル化に違反していますが、コードは違反していません。実際、コードはかつてないほど「カプセル化」されています。

5
Bill

カプセル化に違反しません。あなたはコラボレーターを提供していますが、クラスはそれがどのように使用されるかを決定します。あなたが従う限り 聞かないでください 問題はありません。私はコンストラクター注入が望ましいと思いますが、セッターは賢い限り問題ありません。つまり、クラスが表す不変条件を維持するためのロジックが含まれています。

4
Jason Watkins

これは賛成投票に似ていますが、私は大声で考えたいと思います-おそらく他の人もこのように物事を見るでしょう。

  • クラシカルOOは、コンストラクタを使用して、クラスのコンシューマのパブリック「初期化」コントラクトを定義します(すべての実装の詳細を非表示;カプセル化)。このコントラクトにより、インスタンス化後に、オブジェクトを使用する(つまり、ユーザーが記憶する(つまり、忘れられる)追加の初期化手順はありません)。

  • (コンストラクター)DIは、このパブリックコンストラクターインターフェイスを介してimplementation detailをブリードすることにより、カプセル化を確実に破壊します。ユーザーの初期化コントラクトの定義を担当するパブリックコンストラクターを考慮している限り、カプセル化の恐ろしい違反が発生しています。

理論的な例:

クラスFooには4つのメソッドがあり、初期化には整数が必要なため、コンストラクターはFoo(int size)のようになり、すぐにわかりますクラスFooのユーザーに対して、Fooが機能するためには、インスタンス化時にsizeを提供する必要があることを示します。

このFooの特定の実装では、その作業を行うためにIWidgetも必要になる可能性があるとします。この依存関係のコンストラクター注入により、Foo(int size、IWidget widget)のようなコンストラクターが作成されます

これについて私をいらいらさせるのは、依存関係を持つblending初期化データであるコンストラクタがあることです-1つの入力はクラスのユーザー(size)、もう1つはユーザーを混乱させるだけの内部依存関係であり、実装の詳細です(widget)。

Sizeパラメータは依存関係ではありません-これはインスタンスごとの初期化値です。 IoCは(ウィジェットなどの)外部依存関係に対しては危険ですが、内部状態の初期化に対しては危険です。

さらに悪いことに、このクラスの4つのメソッドのうち2つだけにウィジェットが必要な場合はどうでしょう。ウィジェットが使用されていない場合でも、インスタンス化のオーバーヘッドが発生する可能性があります。

これを妥協/調整する方法は?

1つのアプローチは、操作コントラクトを定義するためにインターフェースに排他的に切り替えることです。ユーザーによるコンストラクタの使用を廃止します。一貫性を保つために、すべてのオブジェクトはインターフェースのみを介してアクセスし、何らかの形式のリゾルバー(IOC/DIコンテナーなど)を介してのみインスタンス化する必要があります。コンテナのみが物事をインスタンス化します。

これでウィジェットの依存関係が処理されますが、Fooインターフェースで個別の初期化メソッドを使用せずに「サイズ」を初期化するにはどうすればよいですか?このソリューションを使用すると、Fooのインスタンスがインスタンスを取得するまでに完全に初期化されるようにする機能が失われました。残念なのは、コンストラクター注入のアイデアとシンプルさが本当に好きだからです。

初期化が外部依存関係だけではない場合、このDIの世界で初期化を保証するにはどうすればよいですか?

4
shawnT

純粋なカプセル化は決して達成できない理想です。すべての依存関係が隠されている場合は、DIはまったく必要ありません。このように考えてください。たとえば、車のオブジェクトの速度の整数値など、オブジェクト内で内部化できるプライベート値が本当にある場合、外部依存関係はなく、その依存関係を反転または注入する必要はありません。純粋にプライベート関数によって操作されるこれらの種類の内部状態値は、常にカプセル化したいものです。

しかし、特定の種類のエンジンオブジェクトを必要とする車を構築している場合は、外部依存関係があります。そのエンジン(たとえば、新しいGMOverHeadCamEngine()など)を内部的にcarオブジェクトのコンストラクター内でインスタンス化して、カプセル化を維持しながら、具体的なクラスGMOverHeadCamEngineへのより巧妙なカップリングを作成するか、または注入して、Carオブジェクトが動作できるようにすることができます。たとえば、具体的な依存関係のないインターフェイスIEngineに不可知的に(そしてより堅牢に)。 IOCコンテナまたは単純なDIを使用してこれを達成するかどうかは重要ではありません。重要なのは、そのため、コードベースがより柔軟になり、副作用が発生しにくくなります。

DIはカプセル化の違反ではありません。これは、当然ながら実質的にすべてのOOPプロジェクト内で当然ながらカプセル化が壊れている場合にカップリングを最小化する方法です。インターフェイスに依存関係を注入すると、カップリングが最小化されます副作用があり、クラスを実装にとらわれないままにすることができます。

3
Dave Sims

それは、依存関係が実際に実装の詳細なのか、クライアントが何らかの方法で知りたい/知りたいと思っているものなのかによって異なります。関連することの1つは、クラスが対象としている抽象化のレベルです。ここではいくつかの例を示します。

内部でキャッシングを使用して呼び出しを高速化するメソッドがある場合、キャッシュオブジェクトはシングルトンか何かで、notを注入する必要があります。キャッシュがまったく使用されていないという事実は、クラスのクライアントが気にする必要のない実装の詳細です。

クラスがデータのストリームを出力する必要がある場合、クラスが結果を配列、ファイル、または他の誰かがデータを送信したい場所に簡単に出力できるように、出力ストリームを注入することはおそらく意味があります。

灰色の領域で、モンテカルロシミュレーションを行うクラスがあるとします。ランダム性のソースが必要です。一方で、これが必要であるという事実は、クライアントが実際にランダム性がどこから来るのかを正確に気にしないという点で、実装の詳細です。一方、実際の乱数ジェネレータは、クライアントが制御したいランダム性の程度、速度などの間でトレードオフを行うため、クライアントはシーディングを制御して反復可能な動作を実現したい場合があるため、注入は意味があります。この場合、乱数ジェネレータを指定せずにクラスを作成する方法を提供し、スレッドローカルのシングルトンをデフォルトとして使用することをお勧めします。より細かい制御が必要になった場合は、ランダム性のソースを注入できる別のコンストラクターを提供してください。

3
dsimcha

私はシンプルに生きています。ドメインクラスにIOC/Dependecyインジェクションを適用しても、関係を記述する外部xmlファイルを作成してコードをメインにするのがはるかに困難になる以外は、改善はありません。 EJB 1.0/2.0やstruts 1.1のような多くのテクノロジーは、XMLに入力するものを減らして元に戻し、注釈などとしてコードに挿入してみます。したがって、開発するすべてのクラスにIOCを適用しますコードを意味のないものにします。

IOCには、依存オブジェクトがコンパイル時に作成する準備ができていない場合に利点があります。これは、インフラストラクチャの抽象的なレベルのアーキテクチャコンポーネントのほとんどで発生し、さまざまなシナリオで機能する必要がある共通の基本フレームワークを確立しようとしました。これらの場所では、使用法IOCがより理にかなっています。それでも、コードがより単純で保守しやすくなるわけではありません。

他のすべてのテクノロジーと同様に、これにもPROとCONがあります。私の心配は、最新のテクノロジーを、コンテキストの最適な使用方法に関係なく、すべての場所に実装することです。

この問題にもう少し取り組んだ結果、依存性注入は(現時点では)カプセル化にある程度違反していると私は考えています。ただし、誤解しないでください。ほとんどの場合、依存関係注入を使用することはトレードオフの価値があると思います。

DIがカプセル化に違反する理由は、作業中のコンポーネントが「外部」パーティー(顧客向けのライブラリを作成することを考える)に配信されるときに明らかになります。

私のコンポーネントがコンストラクタ(またはパブリックプロパティ)を介してサブコンポーネントを注入する必要がある場合、保証はありません

"ユーザーがコンポーネントの内部データを無効または不整合な状態に設定することを防止します。"

同時にそれは言うことはできません

「コンポーネント(その他のソフトウェア)のユーザーは、コンポーネントの機能を知るだけでよく、コンポーネントの機能の詳細に依存することはできません」

どちらの引用も wikipedia からのものです。

具体的な例を挙げましょう。クライアント側DLLを提供する必要があります。これにより、WCFサービス(基本的にはリモートファサード)への通信が簡素化および非表示になります。3つの異なるWCFプロキシクラスに依存しているため、 DIアプローチを採用する場合、コンストラクターを介してそれらを公開することを余儀なくされます。

一般的に私はすべてDIです。この特定の(極端な)例では、危険を感じます。

2
urig

カプセル化は、クラスがオブジェクトを作成する責任(実装の詳細の知識が必要)とクラスを使用する(これらの詳細の知識を必要としない)場合にのみ機能します。その理由を説明しますが、最初に簡単な車の解析を行います。

1971年の古いコンビを運転していたときに、アクセルを押すと、(少し)速くなった。私はその理由を知る必要はありませんでしたが、工場でKombiを作った人たちはその理由を正確に知っていました。

しかし、コーディングに戻ります。 カプセル化は、「その実装を使用して何かから実装の詳細を隠す」ことです。クラスのユーザーが知らなくても実装の詳細が変わる可能性があるため、カプセル化は良いことです。

依存性注入を使用する場合、コンストラクター注入を使用してserviceタイプオブジェクトを構築します(状態をモデル化するエンティティ/値オブジェクトとは対照的)。サービスタイプオブジェクトのメンバー変数は、漏出してはならない実装の詳細を表します。例えばソケットポート番号、データベース資格情報、暗号化を実行するために呼び出す別のクラス、キャッシュなど.

コンストラクタは、クラスが最初に作成されるときに関連します。これは、DIコンテナ(またはファクトリ)がすべてのサービスオブジェクトを相互にワイヤリングしている構築フェーズ中に発生します。 DIコンテナーは、実装の詳細のみを認識します。これは、Kombiファクトリーの人がsparkプラグについて知っているように、実装の詳細についてすべて知っています。

実行時に、作成されたサービスオブジェクトは、実際の作業を行うためにaponと呼ばれます。現時点では、オブジェクトの呼び出し元は実装の詳細を知りません。

それは私が私のコンビをビーチに運転することです。

次に、カプセル化に戻ります。実装の詳細が変更された場合、実行時にその実装を使用するクラスを変更する必要はありません。カプセル化は壊れていません。

私は新しい車をビーチに運転することもできます。カプセル化は壊れていません。

実装の詳細が変更された場合、DIコンテナ(またはファクトリ)を変更する必要があります。そもそも、実装の詳細をファクトリーから隠そうとしていませんでした。

2
WW.

これは単純な考え方かもしれませんが、整数パラメーターを受け取るコンストラクターとパラメーターとしてサービスを受け取るコンストラクターの違いは何ですか?これは、新しいオブジェクトの外部で整数を定義し、それをオブジェクトに供給すると、カプセル化が壊れることを意味しますか?サービスが新しいオブジェクト内でのみ使用されている場合、カプセル化がどのように壊れるかはわかりません。

また、なんらかの自動配線機能(たとえば、C#のAutofac)を使用することで、コードが非常にクリーンになります。 Autofacビルダーの拡張メソッドを作成することで、依存関係のリストが増えるにつれて長期にわたって維持しなければならないであろうDI構成コードの多くを切り取ることができました。

1
blooware

DIは非共有オブジェクトのカプセル化に違反しています-期間。共有オブジェクトは、作成されるオブジェクトの外側に存続期間があるため、作成されるオブジェクトに統合する必要があります。作成されるオブジェクトに対してプライベートなオブジェクトは、作成されたオブジェクトにCOMPOSEDする必要があります。作成されたオブジェクトが破棄されると、作成されたオブジェクトも一緒に使用されます。人体を例にとりましょう。何が構成され、何が集約されるか。 DIを使用する場合、人体コンストラクターには100のオブジェクトが含まれます。たとえば、臓器の多くは(潜在的に)交換可能です。しかし、それらはまだ身体に組み込まれています。血液細胞は、体の中で毎日作成され(そして破壊され)、外部の影響(タンパク質以外)を必要としません。したがって、血液細胞は体内で内部的に作成されます-new BloodCell()。

DIの擁護者は、オブジェクトは決してnew演算子を使用してはならないと主張しています。その「純粋な」アプローチは、カプセル化に違反するだけでなく、オブジェクトを作成しているすべての人にとってのリスコフ置換原則にも違反します。

1
Greg Brand

私もこの考えに苦労しました。最初は、DIコンテナー(Springなど)を使用してオブジェクトをインスタンス化するための「要件」は、フープを飛び越えているように感じられました。しかし、実際には、それは実際にはフープではありません-必要なオブジェクトを作成するためのもう1つの「公開された」方法にすぎません。確かに、「クラス外」の誰かが必要なものを知っているため、カプセル化は「壊れています」が、それを知っているのはシステムの他の部分ではなく、DIコンテナです。 DIは1つのオブジェクトに別のオブジェクトが必要であることを "認識"しているため、特別なことは何も起こりません。

実際、それはさらに良くなります-ファクトリーとリポジトリーに集中することで、DIがまったく関係していることを知る必要すらありません!それは私に蓋をカプセル化に戻します。ふew!

1
n8wrl

PS。 Dependency Injectionを提供することにより、は必ずしも必要ではありませんブレークEncapsulation。例:

obj.inject_dependency(  factory.get_instance_of_unknown_class(x)  );

クライアントコードはまだ実装の詳細を知りません。

1

少なくともDIがカプセル化を著しく弱めることは自明だと思います。これに加えて、DIの他に考慮すべきいくつかの欠点があります。

  1. コードの再利用が難しくなります。明示的に依存関係を提供する必要なしにクライアントが使用できるモジュールは、クライアントがそのコンポーネントの依存関係を何らかの方法で発見し、何らかの方法でそれらを使用可能にする必要があるモジュールよりも明らかに使いやすいです。たとえば、ASPアプリケーションで使用するために最初に作成されたコンポーネントは、クライアントのhttpリクエストに関連するライフタイムをオブジェクトインスタンスに提供するDIコンテナーによって提供される依存関係を予期している場合があります。これは単純ではない場合があります。元のASPアプリケーションと同じ組み込みのDIコンテナーが付属していない別のクライアントで再現するには.

  2. コードをより脆弱にする可能性があります。インターフェース仕様によって提供される依存関係は予期しない方法で実装される可能性があり、静的に解決された具体的な依存関係では不可能なランタイムバグのクラス全体を引き起こします。

  3. それはあなたがそれがどのように機能したいかについてのあなたがより少ない選択で終わるかもしれないという意味でコードをより柔軟にすることができます。すべてのクラスが、所有するインスタンスの存続期間全体にわたってすべての依存関係が存在する必要があるわけではありませんが、多くのDI実装では、他に選択肢はありません。

そのことを頭に入れておくと、最も重要な質問は、「特定の依存関係を外部で指定する必要があるか? "になると思います。実際には、テストをサポートするために依存関係を外部から提供する必要があることはめったにありません。

依存関係を外部から提供する必要がある場合、通常、オブジェクト間の関係が内部の依存関係ではなくコラボレーションであることを示唆しています。この場合、適切な目標はeach1つのクラスを他のクラスの中にカプセル化するのではなく、クラス。

私の経験では、DIの使用に関する主な問題は、組み込みのDIを備えたアプリケーションフレームワークから始めるか、コードベースにDIサポートを追加するかということです。何らかの理由で、正しいはずのDIサポートがあるため、人々はそれを推測しますすべてをインスタンス化する方法。彼らは、「この依存関係を外部で指定する必要があるか」という質問をすることさえありません。さらに悪いことに、everythingのDIサポートも他のすべてのユーザーに強制的に使用させようと試み始めます。

この結果、コードベースは容赦なく進化し始め、コードベースに何かのインスタンスを作成するには、一連の鈍いDIコンテナー構成が必要となり、方法を特定するための追加のワークロードがあるため、何かをデバッグするのは2倍困難になります。そして何がインスタンス化されたか。

質問に対する私の答えはこれです。 DIを使用して、解決する実際の問題を特定できます。他の方法では簡単に解決できません。

1
Neutrino

Encapsulationはやや遠近法に依存していることに言及する価値があるでしょう。

public class A { 
    private B b;

    public A() {
        this.b = new B();
    }
}


public class A { 
    private B b;

    public A(B b) {
        this.b = b;
    }
}

Aクラスに取り組んでいる誰かの観点から見ると、2番目の例では、Athis.bの性質についてあまり理解していません。

一方、DIなし

new A()

new A(new B())

このコードを見る人は、2番目の例のAの性質について詳しく知っています。

DIを使用すると、少なくとも漏洩したすべての知識が1か所に集まります。

0
Tom B

極端に言えば、DIはカプセル化に違反する可能性があることに同意します。通常、DIは真にカプセル化されなかった依存関係を公開します。これは、MiškoHeveryの Singletons is Pathological Liars から借りた単純化された例です。

CreditCardテストから始めて、単純な単体テストを作成します。

_@Test
public void creditCard_Charge()
{
    CreditCard c = new CreditCard("1234 5678 9012 3456", 5, 2008);
    c.charge(100);
}
_

来月、あなたは100ドルの請求書を受け取ります。なぜ請求されたのですか?単体テストは本番データベースに影響を与えました。内部的には、CreditCardはDatabase.getInstance()を呼び出します。コンストラクターでDatabaseInterfaceを取るようにCreditCardをリファクタリングすると、依存関係があるという事実が明らかになります。しかし、CreditCardクラスは外部から見える副作用を引き起こすため、依存関係がカプセル化されていないことは間違いないでしょう。リファクタリングせずにCreditCardをテストする場合は、依存関係を確認できます。

_@Before
public void setUp()
{
    Database.setInstance(new MockDatabase());
}

@After
public void tearDown()
{
    Database.resetInstance();
}
_

データベースを依存関係として公開することでカプセル化が減少するかどうかを心配する価値はないと思います。これは優れた設計だからです。すべてのDIの決定がそれほど単純ではありません。ただし、他のどの回答も反例を示していません。

0
Craig P. Motlin

それは範囲の問題だと思います。カプセル化を定義する場合(方法を知らせない)、カプセル化された機能とは何かを定義する必要があります。

  1. Class as is:カプセル化しているのは、クラスの唯一の責任です。どうやってそれを知っているか。たとえば、並べ替え。注文のためにいくつかのコンパレーターを注入する場合、たとえば、クライアントはそれはカプセル化されたものの一部ではありません:クイックソート。

  2. 設定済み機能:すぐに使用できる機能を提供する場合は、QuickSortクラスではなく、コンパレータで設定されたQuickSortクラスのインスタンスを提供します。その場合、作成と構成を担当するコードは、ユーザーコードから非表示にする必要があります。そして、それがカプセル化です。

クラスをプログラミングする場合、つまり、単一の責任をクラスに実装する場合は、オプション1を使用します。

あなたがアプリケーションをプログラミングしているとき、それはそうです、いくつかの有用なconcrete仕事を引き受けるものを作るなら、あなたは繰り返しオプション2を使用しています。

これは構成されたインスタンスの実装です:

<bean id="clientSorter" class="QuickSort">
   <property name="comparator">
      <bean class="ClientComparator"/>
   </property>
</bean>

これは、他のいくつかのクライアントコードでの使用方法です。

<bean id="clientService" class"...">
   <property name="sorter" ref="clientSorter"/>
</bean>

実装を変更した場合(clientSorter Bean定義を変更した場合)は、クライアントの使用が中断されないため、カプセル化されます。たぶん、すべてをまとめてxmlファイルを使用すると、すべての詳細が表示されます。しかし、私を信じてください、クライアントコード(ClientServiceわからないそのソーターについては何も。

0
helios