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

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

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

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

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

包装A中的A类:

 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中的B类:

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

如我们所见, A取决于B 没有BA不能使用。 但是如果我们想在未来用BetterB取代B呢? 为此,我们在packageA创建了一个Interface Inter

 package packageA; public interface Inter { public void doSomething(); } 

要使用此接口,我们import packageA.Inter; 然后让B implements InterB implements Inter并用A替换AB所有出现。 结果是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(); } } 

此时,我们已经看到从AB的依赖关系已经消失: import packageB.B; 不再需要了。 只有一个问题:我们无法实例化接口的实例。 但是控制的反转来拯救:不是实例化Inter wihtin 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(); } } 

通过这种方法,我们现在可以随意改变A中的Inter的具体实现。 假设我们编写了一个新类BetterB

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

现在我们可以实例化具有不同Inter implementation的A

 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


Adendum

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

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

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

A类需要B的日志记录function,但不关心日志写入的位置。 它不关心DB ,但由于它依赖于B ,它还依赖于DB 。 这不是很理想。

所以你可以做的是将类B分成两个类:一个描述日志记录function的抽象类L (不依赖于DB ),以及依赖于DB的实现。

然后你可以将A类与B分离,因为现在A只依赖于L B现在也依赖于L ,这就是它被称为依赖性反转的原因,因为B提供了L提供的function。

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

但是由于现在A不依赖于B但是(在源中)仅在运行时的抽象接口L ,它必须被设置为使用L一些特定实现(例如B )。 所以需要有其他人告诉A在运行时使用B (或其他东西)。 这就是所谓的控制反转 ,因为在A决定使用B ,现在其他人(例如容器)告诉A在运行时使用B

您描述的情况消除了类A对类B的特定实现的依赖性,并将其替换为接口。 现在,类A可以接受任何实现接口的类型的对象,而不是仅接受类B.设计保留相同的function,因为类B用于实现该接口。

Interesting Posts