私が正しく理解している場合、依存性注入の一般的なメカニズムは、クラスのコンストラクターまたはクラスのパブリックプロパティ(メンバー)を介して注入することです。
これにより、注入される依存関係が明らかになり、カプセル化のOOP原則に違反します。
このトレードオフを特定することは正しいですか?この問題にどのように対処しますか?
以下の自分の質問に対する私の回答も参照してください。
私が正しく理解している場合、依存性注入の一般的なメカニズムは、クラスのコンストラクターまたはクラスのパブリックプロパティ(メンバー)を介して注入することです。
これにより、注入される依存関係が明らかになり、カプセル化のOOP原則に違反します。
このトレードオフを特定することは正しいですか?この問題にどのように対処しますか?
以下の自分の質問に対する私の回答も参照してください。
回答:
この問題には、興味深いと思われる別の見方があります。
IoC /依存性注入を使用する場合、OOPの概念は使用しません。確かに、私たちはオブジェクト指向言語を「ホスト」として使用していますが、IoCの背後にある考え方は、オブジェクト指向ではなくコンポーネント指向のソフトウェアエンジニアリングに由来しています。
コンポーネントソフトウェアは、依存関係の管理に関するものです。一般的な使用例は、.NETのアセンブリメカニズムです。各アセンブリは、参照するアセンブリのリストを公開します。これにより、実行中のアプリケーションに必要な部分をまとめて(そして検証して)より簡単になります。
IoCを介してOOプログラムに同様の手法を適用することにより、プログラムの構成と保守を容易にすることを目指しています。依存関係の公開(コンストラクターのパラメーターなど)は、これの重要な部分です。コンポーネント/サービス指向の世界と同様に、カプセル化は実際には適用されません。詳細を漏らす「実装タイプ」はありません。
残念ながら、現在のところ私たちの言語は、きめの細かいオブジェクト指向の概念を、よりきめの細かいコンポーネント指向の概念から分離していません。そのため、これはあなたが心に留めておかなければならない違いです:)
これは良い質問です。ただし、オブジェクトが依存関係を満たすためには、ある時点で、最も純粋な形式のカプセル化に違反する必要があります。依存関係の一部のプロバイダーは、問題のオブジェクトにが必要であることを知っている必要がFoo
あり、プロバイダーはFoo
オブジェクトにを提供する方法を持っている必要があります。
古典的には、後者のケースは、あなたが言うように、コンストラクター引数またはセッターメソッドを通じて処理されます。ただし、これは必ずしも正しいとは限りません。たとえば、JavaのSpring DIフレームワークの最新バージョンでは、プライベートフィールドに注釈を付けることができます(例:を使用@Autowired
)。依存関係は、リフレクションを介して設定されます。クラスのパブリックメソッド/コンストラクタ。これはあなたが探していた種類の解決策かもしれません。
そうは言っても、コンストラクターの注入がそれほど問題になるとは思いません。オブジェクトは、構築後は完全に有効である必要があると常に感じていました。つまり、役割を実行するために必要なもの(つまり、有効な状態)は、とにかくコンストラクタを通じて提供する必要があります。コラボレーターの動作を必要とするオブジェクトがある場合、コンストラクターがこの要件を公にアドバタイズし、クラスの新しいインスタンスが作成されたときに確実に満たされるようにすることは私には問題ないようです。
理想的には、オブジェクトを処理するときは、とにかくインターフェースを介してオブジェクトとやり取りします。これを行う(およびDIを介して依存関係を配線する)ほど、実際にコンストラクターを扱う必要が少なくなります。理想的な状況では、コードはクラスの具体的なインスタンスを処理したり作成したりすることはありません。そのためIFoo
、コンストラクターがFooImpl
その仕事をする必要があることを示すことを心配することなく、実際にはFooImpl
の存在を意識することなく、DIを介して取得されます。この観点から、カプセル化は完璧です。
これは当然の意見ですが、私の心にDIは必ずしもカプセル化に違反しているわけではなく、実際、内部構造に関する必要なすべての知識を1か所に集中させることで、カプセル化を助けることができます。これはそれ自体が良いことであるだけでなく、この場所が自分のコードベースの外にあるので、クラスの依存関係を知る必要のないコードを書くことができます。
これにより、注入される依存関係が明らかになり、カプセル化のOOP原則に違反します。
まあ、率直に言って、すべてがカプセル化に違反しています。:)それはよく扱われなければならない一種の優しい原則です。
それで、何がカプセル化に違反していますか?
継承は行います。
「継承はサブクラスをその親の実装の詳細に公開するため、しばしば「継承はカプセル化を壊す」と言われています。」(ギャングオブフォー1995:19)
アスペクト指向プログラミング はそうです。たとえば、onMethodCall()コールバックを登録すると、通常のメソッド評価にコードを挿入し、奇妙な副作用などを追加する絶好の機会が得られます。
C ++のフレンド宣言はそうです。
Rubyのクラス拡張はそうします。文字列クラスが完全に定義された後のどこかに文字列メソッドを再定義するだけです。
まあ、多くのものはそうします。
カプセル化は良い重要な原則です。しかし、それだけではありません。
switch (principle)
{
case encapsulation:
if (there_is_a_reason)
break!
}
はい、DIはカプセル化(「情報隠蔽」とも呼ばれます)に違反しています。
しかし、実際の問題は、開発者がKISS(Keep It Short and Simple)およびYAGNI(You Ai n't Gonna Need It)の原則に違反する言い訳としてそれを使用するときに発生します。
個人的には、私はシンプルで効果的なソリューションを好みます。私は主に「新しい」演算子を使用して、必要なときにいつでもどこでもステートフルな依存関係をインスタンス化します。シンプルで、カプセル化されており、理解しやすく、テストも簡単です。では、なぜでしょうか。
優れた依存性注入コンテナー/システムは、コンストラクター注入を可能にします。依存オブジェクトはカプセル化され、公開する必要はまったくありません。さらに、DPシステムを使用することで、オブジェクトの構築方法の詳細を、コードがどれも「認識」することさえありません。この場合、カプセル化はさらに多くなります。コードのほぼすべてが、カプセル化されたオブジェクトの知識から保護されるだけでなく、オブジェクトの構築にも関与しないためです。
ここで、作成したオブジェクトが独自のカプセル化されたオブジェクトを作成する場合(おそらくコンストラクター内)と比較していると仮定します。DPについての私の理解は、この責任をオブジェクトから取り除き、他の誰かに与えたいということです。そのために、この場合のDPコンテナーである「他の誰か」は、カプセル化に「違反」する親密な知識を持っています。利点は、オブジェクト自体からその知識を引き出すことです。誰かが持っている必要があります。アプリケーションの残りの部分はしません。
私はそれをこのように考えます:ディペンデンシーインジェクションコンテナー/システムはカプセル化に違反していますが、コードは違反していません。実際、コードはかつてないほど「カプセル化」されています。
Jeff Sternalが質問へのコメントで指摘したように、答えはカプセル化の定義方法に完全に依存しています。
カプセル化の意味については、2つの主要な陣営があるようです。
File
オブジェクトがへのメソッドがありSave
、Print
、Display
、ModifyText
、などこれら2つの定義は、互いに直接矛盾しています。File
オブジェクトがそれ自体を印刷できる場合、それはプリンターの動作に大きく依存します。一方、それが印刷できるもの(またはそのようなインターフェース)を知っているだけの場合IFilePrinter
、File
オブジェクトは印刷について何も知る必要がないため、オブジェクトを操作することでオブジェクトへの依存関係が少なくなります。
したがって、最初の定義を使用すると、依存性注入によりカプセル化が解除されます。しかし、率直に言って、私が最初の定義が好きかどうかはわかりません-それは明らかにスケーリングしません(そうした場合、MS Wordは1つの大きなクラスになります)。
一方、カプセル化の2番目の定義を使用している場合、依存性注入はほぼ必須です。
カプセル化に違反しません。あなたはコラボレーターを提供していますが、クラスはそれがどのように使用されるかを決定します。Tellに従っている限り、問題はありません。私はコンストラクター注入が望ましいと思いますが、セッターは賢い限り問題ありません。つまり、クラスが表す不変条件を維持するためのロジックが含まれています。
これは賛成投票に似ていますが、私は大声で考えたいです-おそらく他の人も同じように物事を見るでしょう。
従来のOOは、コンストラクターを使用して、クラスのコンシューマー用のパブリックな「初期化」コントラクトを定義します(すべての実装の詳細を非表示、別名はカプセル化)。このコントラクトにより、インスタンス化後、すぐに使用できるオブジェクト(つまり、ユーザーが記憶する(つまり、忘れる)追加の初期化手順がない)ことが保証されます。
(コンストラクター)DIは、このパブリックコンストラクターインターフェースを介して実装の詳細を明確にすることにより、カプセル化を確実に破壊します。ユーザーの初期化コントラクトの定義を担当するパブリックコンストラクターを検討している限り、カプセル化の恐ろしい違反が発生しています。
理論的な例:
クラスFooには4つのメソッドがあり、初期化に整数が必要なため、そのコンストラクターはFoo(int size)のようになり、Fooが機能するためには、インスタンス化時にサイズを指定する必要があることをクラスFooのユーザーにすぐにわかります。
このFooの特定の実装では、その作業を行うためにIWidgetも必要になる可能性があるとします。この依存関係のコンストラクター注入により、Foo(int size、IWidget widget)のようなコンストラクターが作成されます。
これについて私をいらいらさせているのは、初期化データと依存関係をブレンドするコンストラクターがあることです。1つの入力はクラス(size)のユーザーに関係し、もう1つの入力はユーザーを混乱させるだけの内部依存関係であり、実装です。詳細(ウィジェット)。
サイズパラメータは依存関係ではありません。これは、インスタンスごとの初期化値です。IoCは、外部依存関係(ウィジェットなど)に対しては危険ですが、内部状態の初期化に対しては危険です。
さらに悪いことに、このクラスの4つのメソッドのうち2つだけにウィジェットが必要な場合はどうでしょう。ウィジェットが使用されていない場合でも、インスタンス化のオーバーヘッドが発生する可能性があります。
これを妥協/調整する方法は?
1つのアプローチは、操作コントラクトを定義するためにインターフェースに排他的に切り替えることです。ユーザーによるコンストラクタの使用を廃止します。一貫性を保つために、すべてのオブジェクトはインターフェースのみを介してアクセスし、何らかの形式のリゾルバー(IOC / DIコンテナーなど)を介してのみインスタンス化する必要があります。コンテナのみがインスタンスを作成します。
これでウィジェットの依存関係が処理されますが、Fooインターフェースで個別の初期化メソッドを使用せずに「サイズ」を初期化するにはどうすればよいでしょうか。このソリューションを使用すると、インスタンスを取得するまでにFooのインスタンスが完全に初期化されるようにする機能が失われました。残念なのは、コンストラクター注入のアイデアと単純さが本当に好きだからです。
初期化が外部依存関係だけではない場合、このDIの世界で初期化を保証するにはどうすればよいですか?
純粋なカプセル化は、決して達成できない理想です。すべての依存関係が隠されている場合、DIはまったく必要ありません。このように考えてください。たとえば、車のオブジェクトの速度の整数値など、オブジェクト内で内部化できるプライベートな値が本当にある場合、外部依存関係はなく、その依存関係を反転または注入する必要はありません。純粋にプライベート関数によって操作されるこれらの種類の内部状態値は、常にカプセル化したいものです。
しかし、特定の種類のエンジンオブジェクトを必要とする自動車を構築している場合は、外部依存関係があります。そのエンジン(たとえば、新しいGMOverHeadCamEngine()など)をcarオブジェクトのコンストラクター内で内部的にインスタンス化してカプセル化を保持しながら、具体的なクラスGMOverHeadCamEngineへのはるかに巧妙な結合を作成するか、または注入してCarオブジェクトが動作できるようにすることができます例えば、具体的な依存関係のないインターフェースIEngineに不可知的に(そしてはるかに堅牢に)。これを達成するためにIOCコンテナーまたは単純なDIのどちらを使用するかは重要ではありません。重要なのは、さまざまな種類のエンジンをそれらに結合せずに使用できるCarがあり、コードベースがより柔軟になり、副作用が起こりにくい。
DIはカプセル化の違反ではなく、事実上すべてのOOPプロジェクト内で当然のことながらカプセル化が必然的に失敗した場合にカップリングを最小限に抑える方法です。依存関係をインターフェースに外部から注入すると、カップリングの副作用が最小限に抑えられ、クラスは実装にとらわれないままになります。
それは、依存関係が実際に実装の詳細なのか、クライアントが何らかの方法で知りたい/知りたいと思うものなのかによって異なります。関連することの1つは、クラスが対象としている抽象化のレベルです。ここではいくつかの例を示します。
内部でキャッシュを使用して呼び出しを高速化するメソッドがある場合、キャッシュオブジェクトはシングルトンか何かであり、注入されるべきではありません。キャッシュがまったく使用されていないという事実は、クラスのクライアントが気にする必要のない実装の詳細です。
クラスがデータのストリームを出力する必要がある場合、クラスが配列、ファイル、または他の誰かがデータを送信したい場所に結果を簡単に出力できるように、出力ストリームを注入することはおそらく意味があります。
灰色の領域で、モンテカルロシミュレーションを行うクラスがあるとします。ランダム性のソースが必要です。一方で、これが必要であるという事実は、クライアントが実際にランダム性がどこから来るのかを正確に気にしないという点で、実装の詳細です。一方、実際の乱数ジェネレータは、クライアントが制御したいランダム性の程度、速度などの間でトレードオフを行うため、クライアントはシーディングを制御して反復可能な動作を実現したい場合があるため、注入は意味があります。この場合、乱数ジェネレーターを指定せずにクラスを作成する方法を提供し、スレッドローカルのシングルトンをデフォルトとして使用することをお勧めします。より細かい制御が必要になった場合は、ランダム性のソースを注入できる別のコンストラクターを提供します。
私はシンプルに生きています。ドメインクラスにIOC / Dependecyインジェクションを適用しても、関係を記述した外部xmlファイルを作成してコードをメインにするのがはるかに困難になる以外は、改善はありません。EJB 1.0 / 2.0やstruts 1.1などの多くのテクノロジーは、XMLに入力するものを減らして元に戻し、注釈などとしてコードに挿入してみます。したがって、開発するすべてのクラスにIOCを適用すると、コードが意味をなさなくなります。
依存オブジェクトがコンパイル時に作成する準備ができていない場合、IOCには利点があります。これは、インフラストラクチャの抽象的なレベルのアーキテクチャコンポーネントのほとんどで発生し、さまざまなシナリオで機能する必要がある共通の基本フレームワークを確立しようとしました。これらの場所では、IOCの方が理にかなっています。それでも、これはコードをより単純化/保守可能にするものではありません。
他のすべてのテクノロジーと同様に、これにもPROとCONがあります。私の心配は、最新のテクノロジーを、コンテキストの使用状況に関係なくすべての場所に実装することです。
カプセル化は、クラスがオブジェクトを作成する責任(実装の詳細の知識が必要)とクラスを使用する(これらの詳細の知識を必要としない)場合にのみ機能します。その理由を説明しますが、最初に簡単な車の解析を行います。
1971年の古いコンビを運転していたとき、アクセルを押すことができ、それは(少し)速くなりました。私はその理由を知る必要はありませんでしたが、工場でKombiを作った人たちは正確に理由を知っていました。
しかし、コーディングに戻ります。 カプセル化は、「その実装を使用して何かから実装の詳細を隠す」ことです。クラスのユーザーが知らなくても実装の詳細が変わる可能性があるため、カプセル化は良いことです。
依存関係注入を使用する場合、コンストラクター注入を使用して、サービスタイプオブジェクトを作成します(状態をモデル化するエンティティ/値オブジェクトとは対照的)。サービスタイプオブジェクトのメンバー変数は、漏出してはならない実装の詳細を表します。たとえば、ソケットポート番号、データベース資格情報、暗号化を実行するために呼び出す別のクラス、キャッシュなど。
コンストラクタは、クラスを最初に作成されるときに関連しています。これは、DIコンテナ(またはファクトリ)がすべてのサービスオブジェクトを相互にワイヤリングする際の構築フェーズ中に発生します。DIコンテナーは、実装の詳細のみを認識します。Kombi工場のスタッフがスパークプラグについて知っているように、実装の詳細についてもすべて知っています。
実行時に、作成されたサービスオブジェクトは、実際の作業を行うためにaponと呼ばれます。現時点では、オブジェクトの呼び出し元は実装の詳細を何も知りません。
それは私が私のコンビをビーチに運転することです。
次に、カプセル化に戻ります。実装の詳細が変更された場合、実行時にその実装を使用するクラスを変更する必要はありません。カプセル化は壊れていません。
私は新しい車をビーチまで運転することもできます。カプセル化は壊れていません。
実装の詳細が変更された場合、DIコンテナー(またはファクトリー)を変更する必要があります。そもそも、実装の詳細をファクトリーから隠そうとしていませんでした。
この問題にもう少し取り組んだ結果、依存性注入は(現時点では)カプセル化にある程度違反していると私は考えています。ただし、誤解しないでください。ほとんどの場合、依存関係の注入を使用することはトレードオフの価値があると思います。
DIがカプセル化に違反する理由は、作業中のコンポーネントが「外部」パーティー(顧客向けのライブラリを作成することを考える)に引き渡されるときに明らかになります。
私のコンポーネントがコンストラクター(またはパブリックプロパティ)を介してサブコンポーネントを注入する必要がある場合、保証はありません
「ユーザーがコンポーネントの内部データを無効または不整合な状態に設定することを防ぐ」。
同時にとは言えない
「コンポーネント(その他のソフトウェア)のユーザーは、コンポーネントの機能を知るだけでよく、コンポーネントの機能の詳細に依存することはできません。」
両方の引用はウィキペディアからです。
具体例を挙げましょう。WCFサービス(基本的にはリモートファサード)への通信を簡素化および非表示にするクライアント側DLLを提供する必要があります。3つの異なるWCFプロキシクラスに依存しているため、DIアプローチを採用すると、コンストラクターを介してそれらを公開する必要があります。これで、隠そうとしている通信層の内部を公開します。
一般的に私はすべてDIです。この特定の(極端な)例では、危険を感じます。
DIは非共有オブジェクトのカプセル化に違反しています-期間。共有オブジェクトは、作成されるオブジェクトの外側に存続期間があるため、作成されるオブジェクトに統合する必要があります。作成されるオブジェクトにプライベートなオブジェクトは、作成されたオブジェクトにCOMPOSEDする必要があります。作成されたオブジェクトが破棄されると、作成されたオブジェクトも一緒に使用されます。人体を例にとりましょう。何が構成され、何が集約されるか。DIを使用する場合、人体コンストラクターには100のオブジェクトが含まれます。たとえば、臓器の多くは(潜在的に)交換可能です。しかし、それらはまだ身体に組み込まれています。血液細胞は、体内で毎日作成され(そして破壊され)、外部からの影響(タンパク質以外)を必要としません。したがって、血液細胞は体内で内部的に作成されます-new BloodCell()。
DIの擁護者は、オブジェクトは決してnew演算子を使用してはならないと主張しています。その「純粋な」アプローチは、カプセル化に違反するだけでなく、オブジェクトを作成しているすべての人にとってのリスコフ置換原則にも違反します。
私もこの考えに苦労しました。最初は、DIコンテナー(Springなど)を使用してオブジェクトをインスタンス化するための「要件」は、フープを飛び越えているように感じられました。しかし、実際には、それは実際にはフープではありません-必要なオブジェクトを作成するためのもう1つの「公開された」方法にすぎません。確かに、「クラス外」の誰かが必要なものを知っているため、カプセル化は「壊れています」が、それを知っているのはシステムの他の部分ではなく、DIコンテナです。DIは1つのオブジェクトに別のオブジェクトが必要であることを "認識"しているため、特別なことは何も起こりません。
実際、それはさらに良くなります-ファクトリーとリポジトリーに集中することで、DIが関与していることを知る必要すらありません!それは私にふたをカプセル化に戻します。ふew!
PS。依存性注入を提供することで、必ずしもカプセル化を解除する必要はありません。例:
obj.inject_dependency( factory.get_instance_of_unknown_class(x) );
クライアントコードはまだ実装の詳細を知りません。
これは単純な考え方かもしれませんが、整数パラメーターを受け取るコンストラクターとパラメーターとしてサービスを受け取るコンストラクターの違いは何ですか?これは、新しいオブジェクトの外部で整数を定義し、それをオブジェクトに供給すると、カプセル化が壊れることを意味しますか?サービスが新しいオブジェクト内でのみ使用されている場合、それがカプセル化を壊す方法はわかりません。
また、なんらかの自動配線機能(たとえば、C#の場合はAutofac)を使用することで、コードが非常にクリーンになります。Autofacビルダーの拡張メソッドを作成することで、依存関係のリストが増えるにつれて長期にわたって維持しなければならなかったであろう、多くのDI構成コードを切り取ることができました。
少なくともDIがカプセル化を著しく弱めることは自明だと思います。これに加えて、DIの他に考慮すべきいくつかの欠点があります。
コードの再利用が難しくなります。明示的に依存関係を提供する必要なしにクライアントが使用できるモジュールは、クライアントがそのコンポーネントの依存関係を何らかの方法で発見し、何らかの方法でそれらを使用可能にする必要があるモジュールよりも明らかに使いやすいです。たとえば、ASPアプリケーションで使用するために最初に作成されたコンポーネントは、オブジェクトインスタンスにクライアントのhttpリクエストに関連するライフタイムを提供するDIコンテナによって提供される依存関係を期待する場合があります。これは、元のASPアプリケーションと同じ組み込みのDIコンテナーが付属していない別のクライアントで再現するのは簡単ではない場合があります。
コードをより脆弱にする可能性があります。インターフェイス仕様によって提供される依存関係は予期しない方法で実装される可能性があり、静的に解決された具体的な依存関係では不可能なランタイムバグのクラス全体を引き起こします。
それはあなたがそれがどのように動作したいかについてあなたがより少ない選択で終わるかもしれないという意味でコードをより柔軟にすることができます。すべてのクラスが、所有するインスタンスの存続期間全体にわたってすべての依存関係が存在する必要があるわけではありませんが、多くのDI実装では、他に選択肢はありません。
そのことを念頭に置くと、最も重要な質問は、「特定の依存関係を外部で指定する必要があるのでしょうか?」になると思います。実際には、テストをサポートするために依存関係を外部から提供する必要があることはめったにありません。
依存関係を実際に外部から提供する必要がある場合、通常、オブジェクト間の関係は内部の依存関係ではなくコラボレーションであることを示唆しています。この場合、適切な目標は、あるクラスを他のクラスの内部にカプセル化するのではなく、各クラスをカプセル化することです。 。
私の経験では、DIの使用に関する主な問題は、DIが組み込まれたアプリケーションフレームワークから始めるか、コードベースにDIサポートを追加するかということです。何らかの理由で、正しいはずのDIサポートがあるので、人々はそれを推測しますすべてをインスタンス化する方法。彼らは「この依存関係を外部で指定する必要があるか」という質問をすることさえありません。さらに悪いことに、彼らはまたのためのDIサポートを使用して他の皆を強制しようとし始めるすべてをあまりにも。
この結果、コードベースが容赦なく進化し始め、コードベースに何かのインスタンスを作成するには、一連の鈍いDIコンテナー構成が必要になり、どのようにデバッグするかは、方法を特定するための余分なワークロードがあるためです。そして何がインスタンス化されたか。
質問に対する私の答えはこれです。DIを使用して、DIが解決する実際の問題を特定できます。これは、他の方法では簡単に解決できません。
極端に言えば、DIはカプセル化に違反する可能性があることに同意します。通常、DIは真にカプセル化されなかった依存関係を公開します。以下は、MiškoHeveryから借りた単純化された例です。シングルトンは病理学的嘘つきです:
CreditCardテストから始めて、単純な単体テストを作成します。
@Test
public void creditCard_Charge()
{
CreditCard c = new CreditCard("1234 5678 9012 3456", 5, 2008);
c.charge(100);
}
来月は100ドルの請求書が届きます。なぜ請求されたのですか?単体テストは本番データベースに影響を与えました。内部的には、CreditCardはを呼び出しますDatabase.getInstance()
。CreditCardをリファクタリングDatabaseInterface
してコンストラクタでaを取得すると、依存関係があることが明らかになります。しかし、CreditCardクラスは外部から見える副作用を引き起こすため、依存関係がカプセル化されていないことは間違いないでしょう。リファクタリングせずにCreditCardをテストする場合は、依存関係を確認できます。
@Before
public void setUp()
{
Database.setInstance(new MockDatabase());
}
@After
public void tearDown()
{
Database.resetInstance();
}
データベースを依存関係として公開することでカプセル化が減少するかどうかを心配する価値はないと思います。これは優れた設計だからです。すべてのDIの決定がそれほど単純ではありません。ただし、他のどの回答も反例を示していません。
CreditCard
クラスの外部にあるものを変更することなく、オブジェクトをWebAPIからPayPalに変更するにはどうすればよいですか?
範囲の問題だと思います。カプセル化を定義するとき(方法を知らせない)、カプセル化された機能とは何かを定義する必要があります。
そのままのクラス:カプセル化しているのは、クラスの唯一の責任です。どうやってそれを知っているか。たとえば、並べ替え。注文のためにコンパレーターを注入する場合、たとえば、クライアントはそれがカプセル化されたものの一部ではありません:クイックソート。
構成された機能:すぐに使用できる機能を提供する場合は、QuickSortクラスではなく、コンパレータで構成されたQuickSortクラスのインスタンスを提供します。その場合、作成と構成を担当するコードは、ユーザーコードから非表示にする必要があります。そしてそれがカプセル化です。
クラスをプログラミングする場合、つまり、単一の責任をクラスに実装する場合は、オプション1を使用します。
アプリケーションをプログラミングしているときは、有用な具体的な作業を行う何かを作成してから、オプション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
)
はそのソーターについて何も知りません。
これ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番目の例 A
では、this.b
一方、DIなし
new A()
対
new A(new B())
このコードを見る人A
は、2番目の例のの性質について詳しく知っています。
DIを使用すると、少なくとも漏洩したすべての知識が1か所に集まります。