web-dev-qa-db-ja.com

インターフェイスレベルで2つのクラスを分離するとはどういう意味ですか?

パッケージAにクラスAがあり、パッケージBにクラスBがあるとします。クラスAのオブジェクトがクラスBを参照している場合、2つのクラスはそれらの間で結合していると言われます。

カップリングに対処するには、パッケージBのクラスによって実装されるパッケージAのインターフェイスを定義することをお勧めします。そうすると、クラスAのオブジェクトはパッケージAのインターフェイスを参照できます。これは多くの場合、「依存性の逆転」の例です。

これは「インターフェイスレベルで2つのクラスを分離する」例です。はいの場合、2つのクラスが結合されたときに、クラス間の結合を削除し、同じ機能を維持するにはどうすればよいですか?

17
Sarit Adhikari

架空の例を作成しましょう。

パッケージ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.");
    }
}

ご覧のとおり、ABに依存します。 Bがないと、Aは使用できません。しかし、将来BBetterBに置き換えたい場合はどうでしょうか。このために、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-実装でIntersをインスタンス化できます。

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について穏やかに怒鳴ります)。これは、見返りとして、高レベルの実装がインターフェイスを介してセグレタゲットされる場合、低レベルの実装について知る必要がないことを意味します。したがって、上に示したように、より低いレベルを自由に交換できます。

43
Turing85

Bの機能が、あるデータベースにログを書き込むことであると想像してください。クラスBはクラスDBの機能に依存し、他のクラスへのロギング機能のためのいくつかのインターフェースを提供します。

クラスAにはBのロギング機能が必要ですが、ログがどこに書き込まれるかは関係ありません。 DBは関係ありませんが、Bに依存するため、DBにも依存します。これはあまり望ましくありません。

したがって、できることは、クラスBを2つのクラスに分割することです。ロギング機能を説明する抽象クラスLDBに依存しない)と実装です。 DBによって異なります。

次に、クラスABから切り離すことができます。これは、ALにのみ依存するためです。 BLに依存するようになりました。これは、BLで提供される機能を提供するため、依存性逆転と呼ばれる理由です。

AはリーンLのみに依存するようになったため、DBに依存せずに、他のロギングメカニズムで簡単に使用できます。例えば。 Lで定義されたインターフェースを実装して、単純なコンソールベースのロガーを作成できます。

しかし、今からABに依存せず、(ソースでは)実行時に抽象インターフェースLにのみ依存するため、の特定の実装を使用するように設定する必要があります。 L(たとえば、B)。したがって、実行時にA(または他の何か)を使用するようにBに指示する誰かが必要です。そして、それは制御の反転と呼ばれます。これは、ABを使用することを決定する前に、他の誰か(たとえば、コンテナー)がAに実行時にBを使用します。

5
Gregor Raýman

あなたが説明する状況は、クラスAがクラスBの特定の実装に持っている依存関係を取り除き、それをインターフェースに置き換えます。これで、クラスAは、クラスBだけを受け入れるのではなく、インターフェイスを実装するタイプのオブジェクトを受け入れることができます。クラスBはそのインターフェイスを実装するように作成されているため、デザインは同じ機能を保持します。

3
Bill the Lizard