Loose Coupling in Java

2025年5月10日 | 阅读 8 分钟

Java 项目中最关键的方面之一就是松耦合。Java 中的松耦合展示了如何在 Java 项目或程序中实现松耦合。项目或程序中的松耦合结构越多越好。在松耦合中,一个方法或类几乎是独立的,它们之间的依赖性较低。换句话说,一个类或方法对另一个类或方法的了解越多,开发的耦合结构就越紧密。如果类或方法对彼此的了解越少,就会产生越松耦合的结构。

抽象是关键

为了实现松耦合,在进行继承时应使用抽象类或接口。以下示例将使概念更清晰。

文件名: CouplingExample.java

输出

Inside the foo method of derived class.

说明: 代码很简单易懂。程序中有两个类。一个是基类,另一个是派生类。在 main 方法中实例化派生类,并调用其 foo 方法。

然而,上述代码存在一个问题。继承导致了类 A 和 B 的紧密耦合。类 B 了解类 A 的很多细节。而且,类 A 的变化很有可能影响到类 B。让我们修改上面的代码来理解它。

假设,需要向基类添加一个包含两个整数参数的构造函数。为了满足这一要求,我们在基类中添加了一个构造函数。

文件名: CouplingExample1.java

输出

/CouplingExample1.java:17: error: constructor A in class A cannot be applied to given types;
class B extends A
^
  required: int,int
  found: no arguments
  reason: actual and formal argument lists differ in length
1 error

说明: 输出表明子类 B 中出现了一个错误。然而,我们并没有修改类 B 的任何内容,却出现了错误。这是因为继承导致了紧密耦合的结构。理想情况下,程序或项目中应该避免这种情况。

松耦合代码

让我们使用接口重写上面的代码。

文件名: CouplingExample2.java

输出

In the foo method of class B.

说明: 现在,类 A 的参数化构造函数不会影响类 B。这是因为类 A 和类 B 都依赖于抽象,在本例中是接口。请注意,不仅参数化构造函数,如果类 A 中进行了其他任何更改,也不会影响类 B,反之亦然。

在上面的代码中,类 A 和类 B 是松耦合的,因为它们之间没有直接的依赖关系。此外,我们对类 A 所做的任何更改都不会对类 B 可见。

松耦合的优点

  • 松耦合可以轻松地进行代码更改。
  • 与紧密耦合的结构相比,松耦合结构的测试更容易。
  • 在松耦合结构中进行更改所需的代码量比紧密耦合结构少。

让我们通过一个例子来理解。

文件名: CouplingExample3.java

输出

The computer is using the Dell keyboard.

说明: 在上面的代码中,keyboardUsed() 方法与 Dell 键盘紧密耦合。这意味着 Computer 类不接受任何其他类型的键盘。如果我们使用联想键盘,就会出现编译错误。

文件名: CouplingExample4.java

输出

/CouplingExample3.java:50: error: incompatible types: LenovoKeyboard cannot be converted to DellKeyboard
obj.keyboardUsed(lk);

从实际角度来看,计算机应该能够完美地使用 Dell 或联想键盘。然而,在我们的示例中并非如此。为了让计算机能够使用联想键盘,我们必须在 Computer 类中添加另一个方法。

文件名: CouplingExample5.java

输出

The computer is using the Lenovo keyboard.

说明: 现在,计算机可以处理联想键盘。然而,keyboardUsed() 方法与联想键盘以及 Dell 键盘都紧密耦合。因此,keyboardUsed() 方法不考虑任何其他类型的键盘。因此,我们遇到了同样的问题。此外,在 Computer 类中为特定键盘添加方法也不是一个好主意。

想想如果我们有 50 种键盘会发生什么?处理 50 个方法无疑是一项繁琐的任务。此外,为了添加 50 个方法,人们必须编写大量代码。另外,出于测试目的,我们必须测试所有添加的 50 个方法,这将非常耗时。

为了避免此类问题,我们必须使 keyboardUsed() 方法与键盘松耦合。为了实现这一点,我们的 keyboardUsed() 方法应该依赖于接口(请记住!抽象是关键)。请注意以下代码中的修改。

文件名: CouplingExample6.java

输出

The computer is using the Lenovo keyboard.
The computer is using the Dell keyboard.

说明: 我们看到一个方法可以处理 Dell 或联想公司制造的键盘。这是因为 Computer 类的唯一方法 keyboardUsed() 并没有紧密地与 DellKeyboard 或 LenovoKeyboard 类绑定。keyboardUsed() 方法依赖于抽象(接口 Keyboard)。因此,该方法能够处理任何类型的键盘。

因此,即使我们添加 50 种更多类型的键盘,keyboardUsed() 方法也能处理。因此,与之前的代码相比,我们编写的代码量更少。

测试上面的代码也更容易,因为 Computer 类中只有一个方法。以前,对于每种类型的键盘,我们都必须测试 Computer 类中的所有方法。

此外,如果将来 Dell 公司停止生产键盘,那么我们可以删除 DellKeboard 类。Computer 类无需更改任何内容。然而,在之前的示例中,我们必须从 Computer 类中删除 keyboardUsed(DellKeyboard dk) 方法。

我们可以看到,如果我们添加或删除一些东西,就需要对之前的代码进行更多更改,而对上面的代码进行更少的更改。因此,很明显,为什么人们应该努力实现松耦合结构。

日常生活中的松耦合

我们的日常生活中有很多松耦合的实例。以下是其中一些实例。

  • 时钟和电池:我们家里都有时钟。时钟需要电池才能运行。请注意,时钟与使用的电池松耦合,即无论使用 Duracell 电池、Nippo 电池、Eveready 电池还是其他任何公司的电池,时钟都没有问题。
  • 手机和充电器:手机的充电器与手机不是紧密耦合的。例如,三星手机可以使用小米充电器充电,反之亦然。
  • 电脑和鼠标:来自某个供应商的计算机可以很好地与来自另一个供应商的鼠标配合使用。因此,我们可以说计算机和鼠标是松耦合的。
  • 灯泡座和灯泡:灯泡座可以容纳任何类型的灯泡。它不依赖于使用的灯泡类型。例如,LED 或白炽灯泡可以放在同一个灯泡座中。