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

假设我们在包 A 中有类 A,在包 B 中有类 B。如果类 A 的对象引用类 B,则称这两个类在它们之间具有耦合。

为了解决耦合问题,建议在包 A 中定义一个接口,该接口由包 B 中的类实现。那么类 A 的对象可以引用包 A 中的接口。这通常是“依赖关系反转”中的一个例子。

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


答案 1

让我们创建一个两个类和的虚构示例。AB

包装中的类 :ApackageA

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();
    }
}

包装中的类 :BpackageB

package packageB;

public class B {
    public void doSomething() {
        System.out.println("B did something.");
    }
}

正如我们所看到的,取决于 .没有 ,则不能使用。我们说这与 紧密耦合。如果我们将来想用一个?为此,我们在以下位置创建一个接口:ABBAABBBetterBInterpackageA

package packageA;

public interface Inter {
    public void doSomething();
}

为了利用这个界面,我们

  • 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();
    }
}

我们已经可以看到,从 到 的依赖关系已经消失:不再需要 。只有一个问题:我们无法实例化接口的实例。但是控制反转是有好处的:构造函数不会实例化构造函数中的类型,而是需要一些作为参数的东西:ABimport packageB.B;InterAimplements 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();
    }
}

通过这种方法,我们现在可以随意改变内部的具体实施。假设我们写一个新类:InterABetterB

package packageB;

import packageA.Inter;

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

现在我们可以用不同的 -实现来实例化 s:AInter

Inter b = new B();
A aWithB = new A(b);
aWithB.doSomethingThatUsesInter();

Inter betterB = new BetterB();
A aWithBetterB = new A(betterB);
aWithBetterB.doSomethingThatUsesInter();

而且我们不必在 .代码现在是解耦的,我们可以随意更改的具体实现,只要的合约是(正在)满足的。最值得注意的是,我们可以支持将来将要编写的代码并实现 。AInterInterInter


附录

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

在文献中,这种方法被称为界面分离原理,属于SOLID原则鲍勃叔叔在YouTube上有一个很好的演讲(有趣的是大约15分钟长),展示了如何使用多态性和接口来让编译时依赖关系指向控制流(建议观众的自由裁量权,鲍勃叔叔会温和地咆哮Java)。作为回报,这意味着高级实现不需要知道较低级别的实现,当它们通过接口分离时。因此,正如我们上面所示,可以随意交换较低的水平。


答案 2

想象一下,的功能是将日志写入某个数据库。该类依赖于类的功能,并为其他类的日志记录功能提供了一些接口。BBDB

类需要 的日志记录功能,但不关心日志写入的位置。它不在乎,但既然它依赖于,它也依赖于 。这不是很可取的。ABDBBDB

因此,您可以做的是将类拆分为两个类:描述日志记录功能的抽象类(不依赖于 ),以及取决于 的实现。BLDBDB

然后,您可以将类与 分离,因为现在将仅依赖于 。 现在还依赖于 ,这就是为什么它被称为依赖关系反转,因为它提供了 中提供的功能。ABALBLBL

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

但是由于现在不依赖于,而是(在源代码中)只在运行时依赖于抽象接口,因此必须将其设置为使用某些特定的实现(例如)。因此,需要有其他人告诉在运行时使用(或其他东西)。这被称为控制反转,因为在决定使用之前,但现在其他人(例如容器)告诉在运行时使用。ABLLBABABAB


推荐