在接口级别解耦两个类意味着什么?

Sar*_*ari 15 java spring inversion-of-control decoupling

假设我们在包A中有A类,在包B中有B类.如果类A的对象引用了类B,那么这两个类被认为在它们之间具有耦合.

为了解决耦合问题,建议在包A中定义一个接口,该接口由包B中的类实现.然后,类A的对象可以引用包A中的接口.这通常是"依赖倒置"的一个例子.

这是"在接口级别解耦两个类"的示例.如果是,那么它如何消除类之间的耦合并在两个类耦合时保留相同的功能?

Tur*_*g85 40

让我们创建一个虚构的例子.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

B包装类packageB:

package packageB;

public class B {
    public void doSomething() {
        System.out.println("B did something.");
    }
}
Run Code Online (Sandbox Code Playgroud)

正如我们所见,A取决于B.没有B,A不能用.但是,如果我们想B在未来取代一个BetterB怎么办?为此,我们InterpackageA以下内容中创建一个接口:

package packageA;

public interface Inter {
    public void doSomething();
}
Run Code Online (Sandbox Code Playgroud)

要使用这个接口,我们import packageA.Inter;B implements InterB和替换出现的所有BAInter.结果是这个修改版本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();
    }
}
Run Code Online (Sandbox Code Playgroud)

在这一点上,我们已经看到,从依赖AB消失:将import packageB.B;不再需要.只有一个问题:我们无法实例化接口的实例.但是控制的反转来拯救:不是实例化某种类型的Interwihtin 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

通过这种方法,我们可以改变现在的具体实施InterA随意.假设我们写了一个新类BetterB:

package packageB;

import packageA.Inter;

public class BetterB implements Inter {
    @Override
    public void doSomething() {
        System.out.println("BetterB did something.");
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以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();
Run Code Online (Sandbox Code Playgroud)

我们没有必要改变任何内容A.代码现在已经解耦Inter,只要符合合同,我们就可以随意更改具体实现Inter.最值得注意的是,我们可以支持代码,这些代码将在未来编写并实现Inter.


Adendum

两年前我写了这个答案.虽然总体上对答案感到满意,但我一直认为缺少了某些东西,我想我终于知道它是什么了.以下内容对于理解答案没有必要,但旨在引起读者的兴趣,并为进一步的自我教育提供一些资源.

在文献中,这种方法被称为界面分离原则,属于SOLID原则.鲍勃叔叔在YouTube上有一个很好的谈话(有趣的是大约15分钟),展示了如何使用多态性和接口让编译时依赖性指向控制流(建议观众自行决定,鲍勃叔叔将会关于Java的轻微咆哮).反过来,这意味着高级实现在通过接口进行segretaget时不需要了解更低级别的实现.因此,如上所示,可以随意交换较低的水平.

  • +1 控制反转来救援。这是我见过的最好的控制关系解耦和反转。我读过关于这个主题的书。 (2认同)

Gre*_*man 5

想象一下,的功能B是将日志写入某些数据库。该类B取决于该类的功能,DB并提供一些接口以将其记录到其他类。

A需要的日志记录功能B,但不在乎日志的写入位置。它并不在乎DB,但由于它取决于B,所以它也取决于DB。这不是很理想。

因此,您可以做的是将该类B分为两个类:L描述日志功能的抽象类(不依赖DB)和实现依赖DB

然后你就可以去耦类AB,因为现在A只能依靠LB现在还取决于L,这就是为什么它被称为依赖反转的原因,因为B它提供了中提供的功能L

由于A现在仅依靠精益L,您可以轻松地将其与其他日志记录机制一起使用,而不必依赖DB。例如,您可以创建一个简单的基于控制台的记录器,实现中定义的接口L

但由于现在A不取决于B但(在源)只在抽象接口L在运行时它必须被设置为使用的一些具体实施LB例如)。因此,需要有人告诉运行时A使用B(或其他方式)。这就是所谓的控制反转,因为在A决定使用之前B,但是现在其他人(例如容器)告诉运行时A使用B