パッケージAにクラスAがあり、パッケージBにクラスBがあるとします。クラスAのオブジェクトがクラスBを参照している場合、2つのクラスはそれらの間で結合していると言われます。
カップリングに対処するには、パッケージBのクラスによって実装されるパッケージAのインターフェイスを定義することをお勧めします。そうすると、クラスAのオブジェクトはパッケージAのインターフェイスを参照できます。これは多くの場合、「依存性の逆転」の例です。
これは「インターフェイスレベルで2つのクラスを分離する」例です。はいの場合、2つのクラスが結合されたときに、クラス間の結合を削除し、同じ機能を維持するにはどうすればよいですか?
架空の例を作成しましょう。
パッケージA
のクラスpackageA
:
package packageA;
import packageB.B;
public class A {
private B myB;
public A() {
this.myB = new B();
}
public void doSomethingThatUsesB() {
System.out.println("Doing things with myB");
this.myB.doSomething();
}
}
パッケージB
のクラスpackageB
:
package packageB;
public class B {
public void doSomething() {
System.out.println("B did something.");
}
}
ご覧のとおり、A
はB
に依存します。 B
がないと、A
は使用できません。しかし、将来B
をBetterB
に置き換えたい場合はどうでしょうか。このために、Inter
内にインターフェースpackageA
を作成します。
package packageA;
public interface Inter {
public void doSomething();
}
このインターフェースを利用するために、
import packageA.Inter;
そしてB implements Inter
in B
およびB
内のA
のすべての出現箇所をInter
に置き換えます。結果は、この修正バージョンのA
です。
package packageA;
public class A {
private Inter myInter;
public A() {
this.myInter = ???; // What to do here?
}
public void doSomethingThatUsesInter() {
System.out.println("Doing things with myInter");
this.myInter.doSomething();
}
}
A
からB
への依存関係がなくなったことはすでにわかります:import packageB.B;
はもう必要ありません。 1つだけ問題があります。それは、インターフェースのインスタンスをインスタンス化できないことです。しかし 制御の反転 が助けになります:Inter
のコンストラクター内でタイプA
の何かをインスタンス化する代わりに、コンストラクターはimplements Inter
パラメータとして:
package packageA;
public class A {
private Inter myInter;
public A(Inter myInter) {
this.myInter = myInter;
}
public void doSomethingThatUsesInter() {
System.out.println("Doing things with myInter");
this.myInter.doSomething();
}
}
このアプローチにより、Inter
内のA
の具体的な実装を自由に変更できるようになりました。新しいクラスBetterB
を作成するとします。
package packageB;
import packageA.Inter;
public class BetterB implements Inter {
@Override
public void doSomething() {
System.out.println("BetterB did something.");
}
}
これで、さまざまなA
-実装でInter
sをインスタンス化できます。
Inter b = new B();
A aWithB = new A(b);
aWithB.doSomethingThatUsesInter();
Inter betterB = new BetterB();
A aWithBetterB = new A(betterB);
aWithBetterB.doSomethingThatUsesInter();
そして、A
内で何も変更する必要はありませんでした。これでコードが分離され、Inter
のコントラクトが満たされている限り、Inter
の具体的な実装を自由に変更できます。最も注目すべきは、将来記述され、Inter
を実装するコードをサポートできることです。
補遺
私は2年以上前にこの答えを書きました。全体的に満足しているうちに、何か足りないものがあるといつも思っていたので、ようやくわかったと思います。以下は答えを理解するために必要ではありませんが、読者にspark興味を持って、さらに独学のためのいくつかのリソースを提供することを目的としています。
文献では、このアプローチは インターフェイス分離の原則 として知られており、 [〜#〜] solid [〜#〜]-原則 。 YouTubeのボブおじさんからの素敵な話(興味深いビットは約15分です) ポリモーフィズムとインターフェイスを使用して、コンパイル時の依存関係が制御の流れに逆らうようにする方法を示しています(視聴者の裁量をお勧めします。ボブおじさんはJavaについて穏やかに怒鳴ります)。これは、見返りとして、高レベルの実装がインターフェイスを介してセグレタゲットされる場合、低レベルの実装について知る必要がないことを意味します。したがって、上に示したように、より低いレベルを自由に交換できます。
B
の機能が、あるデータベースにログを書き込むことであると想像してください。クラスB
はクラスDB
の機能に依存し、他のクラスへのロギング機能のためのいくつかのインターフェースを提供します。
クラスA
にはB
のロギング機能が必要ですが、ログがどこに書き込まれるかは関係ありません。 DB
は関係ありませんが、B
に依存するため、DB
にも依存します。これはあまり望ましくありません。
したがって、できることは、クラスB
を2つのクラスに分割することです。ロギング機能を説明する抽象クラスL
(DB
に依存しない)と実装です。 DB
によって異なります。
次に、クラスA
をB
から切り離すことができます。これは、A
がL
にのみ依存するためです。 B
もL
に依存するようになりました。これは、B
がL
で提供される機能を提供するため、依存性逆転と呼ばれる理由です。
A
はリーンL
のみに依存するようになったため、DB
に依存せずに、他のロギングメカニズムで簡単に使用できます。例えば。 L
で定義されたインターフェースを実装して、単純なコンソールベースのロガーを作成できます。
しかし、今からA
はB
に依存せず、(ソースでは)実行時に抽象インターフェースL
にのみ依存するため、の特定の実装を使用するように設定する必要があります。 L
(たとえば、B
)。したがって、実行時にA
(または他の何か)を使用するようにB
に指示する誰かが必要です。そして、それは制御の反転と呼ばれます。これは、A
がB
を使用することを決定する前に、他の誰か(たとえば、コンテナー)がA
に実行時にB
を使用します。
あなたが説明する状況は、クラスAがクラスBの特定の実装に持っている依存関係を取り除き、それをインターフェースに置き換えます。これで、クラスAは、クラスBだけを受け入れるのではなく、インターフェイスを実装するタイプのオブジェクトを受け入れることができます。クラスBはそのインターフェイスを実装するように作成されているため、デザインは同じ機能を保持します。