在接口级别解耦两个类意味着什么?
假设我们在包 A 中有类 A,在包 B 中有类 B。如果类 A 的对象引用类 B,则称这两个类在它们之间具有耦合。
为了解决耦合问题,建议在包 A 中定义一个接口,该接口由包 B 中的类实现。那么类 A 的对象可以引用包 A 中的接口。这通常是“依赖关系反转”中的一个例子。
这是“在接口级别解耦两个类”的示例吗?如果是,当两个类耦合时,它如何消除类之间的耦合并保留相同的功能?
假设我们在包 A 中有类 A,在包 B 中有类 B。如果类 A 的对象引用类 B,则称这两个类在它们之间具有耦合。
为了解决耦合问题,建议在包 A 中定义一个接口,该接口由包 B 中的类实现。那么类 A 的对象可以引用包 A 中的接口。这通常是“依赖关系反转”中的一个例子。
这是“在接口级别解耦两个类”的示例吗?如果是,当两个类耦合时,它如何消除类之间的耦合并保留相同的功能?
让我们创建一个两个类和的虚构示例。A
B
包装中的类 :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
A
B
B
BetterB
Inter
packageA
package packageA;
public interface Inter {
public void doSomething();
}
为了利用这个界面,我们
import packageA.Inter;
并让进入和B implements Inter
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;
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.");
}
}
现在我们可以用不同的 -实现来实例化 s:A
Inter
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
附录
我在2015年写了这个答案。虽然对答案总体上感到满意,但我一直认为缺少了一些东西,我想我终于知道它是什么了。以下内容不是理解答案的必要条件,但旨在激发读者的兴趣,并为进一步的自我教育提供一些资源。
在文献中,这种方法被称为界面分离原理,属于SOLID原则。鲍勃叔叔在YouTube上有一个很好的演讲(有趣的是大约15分钟长),展示了如何使用多态性和接口来让编译时依赖关系指向控制流(建议观众的自由裁量权,鲍勃叔叔会温和地咆哮Java)。作为回报,这意味着高级实现不需要知道较低级别的实现,当它们通过接口分离时。因此,正如我们上面所示,可以随意交换较低的水平。
想象一下,的功能是将日志写入某个数据库。该类依赖于类的功能,并为其他类的日志记录功能提供了一些接口。B
B
DB
类需要 的日志记录功能,但不关心日志写入的位置。它不在乎,但既然它依赖于,它也依赖于 。这不是很可取的。A
B
DB
B
DB
因此,您可以做的是将类拆分为两个类:描述日志记录功能的抽象类(不依赖于 ),以及取决于 的实现。B
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