在接口级别解耦两个类意味着什么?
假设我们在包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
没有B
, A
不能使用。 但是如果我们想在未来用BetterB
取代B
呢? 为此,我们在packageA
创建了一个Interface Inter
:
package packageA; public interface Inter { public void doSomething(); }
要使用此接口,我们import packageA.Inter;
然后让B implements Inter
在B implements Inter
并用A
替换A
中B
所有出现。 结果是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
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用于实现该接口。