Java 在接口级别解耦两个类意味着什么?
假设A包中有A类,B包中有B类。若类A的对象引用了类B,那个么这两个类之间就称为耦合 为了解决耦合问题,建议在包A中定义一个接口,该接口由包B中的类实现。然后,类A的对象可以引用包A中的接口。这通常是“依赖倒置”中的一个例子Java 在接口级别解耦两个类意味着什么?,java,spring,inversion-of-control,decoupling,Java,Spring,Inversion Of Control,Decoupling,假设A包中有A类,B包中有B类。若类A的对象引用了类B,那个么这两个类之间就称为耦合 为了解决耦合问题,建议在包A中定义一个接口,该接口由包B中的类实现。然后,类A的对象可以引用包A中的接口。这通常是“依赖倒置”中的一个例子 这就是“在接口级别解耦两个类”的例子吗。如果是,当两个类耦合时,它如何消除类之间的耦合并保持相同的功能?您描述的情况消除了类A对类B的特定实现的依赖,并用接口替换它。现在,A类可以接受实现接口类型的任何对象,而不是只接受B类。设计保留了相同的功能,因为B类用于实现该接口。想
这就是“在接口级别解耦两个类”的例子吗。如果是,当两个类耦合时,它如何消除类之间的耦合并保持相同的功能?您描述的情况消除了类A对类B的特定实现的依赖,并用接口替换它。现在,A类可以接受实现接口类型的任何对象,而不是只接受B类。设计保留了相同的功能,因为B类用于实现该接口。想象一下,
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
。让我们创建一个虚拟的例子,包含两个类A
和B
包装packageA
中的类别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();
}
}
package packageA;
public interface Inter {
public void doSomething();
}
包装中的类B
:
package packageB;
public class B {
public void doSomething() {
System.out.println("B did something.");
}
}
正如我们所看到的,A
依赖于B
。如果没有B
,A
,则无法使用。我们说A
与B
紧密耦合。如果我们想在将来用一个更好的B
来取代B
,该怎么办?为此,我们在packageA
中创建一个接口Inter
:
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();
}
}
package packageA;
public interface Inter {
public void doSomething();
}
为了利用这个接口,我们
并让import packageA.Inter
,以及B在
中实现InterB
- 将
中出现的A
替换为B
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
的依赖关系消失了:导入包B.B代码>不再需要。只有一个问题:我们不能实例化接口的实例。但总而言之:构造函数不会在A
的构造函数中实例化Inter
类型的东西,而是要求实现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.");
}
}
现在,我们可以使用不同的内部实现来实例化A
s-实现:
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
我在2015年写下了这个答案。虽然我对答案总体上感到满意,但我一直认为遗漏了什么,我想我终于知道了它是什么。以下内容不是理解答案所必需的,但旨在激发读者的兴趣,并为进一步的自我教育提供一些资源
在文献中,这种方法被称为并属于。有一个例子展示了如何使用多态性和接口来让编译时依赖关系指向控制流(建议查看者自行决定,Bob叔叔会温和地抱怨Java)。作为回报,这意味着高级实现在通过接口时不需要了解低级实现。因此,较低的级别可以随意交换,正如我们上面所示。这就是DI(依赖注入)框架真正的亮点所在
当您构建接口时,实际上是在构建用于实现的契约。您的呼叫服务将仅与con进行交互