Coupling in Java – Java中的耦合

最后修改: 2022年 10月 20日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’ll learn about coupling in Java, including types and a description of each one of them. In the end, we briefly describe the Dependency Inversion principle and the Inversion of Control and how these are related to coupling.

在本教程中,我们将学习Java中的耦合,包括类型和对每个类型的描述。最后,我们简要介绍一下依赖反转原则和控制反转以及这些与耦合的关系。

2. Coupling in Java

2.Java中的耦合

When we talk about coupling, we describe the degree to which classes within our system depend on each other. Our goal during the development process is to reduce coupling.

当我们谈论耦合时,我们描述的是我们系统中的类相互依赖的程度。我们在开发过程中的目标是降低耦合度。

Consider the following scenario. We’re designing a metadata collector application. This application collects metadata for us. It fetches metadata in XML format, then exports fetched metadata to a CSV file, and that’s all. An initial approach could be, as we can appreciate in the design:

考虑一下下面的情况。我们正在设计一个元数据收集器应用程序。这个应用程序为我们收集元数据。它以XML格式获取元数据,然后将获取的元数据导出到CSV文件中,仅此而已。一个最初的方法可以是,正如我们在设计中可以体会到的。

Metadata collector diagrama tight coupling

Metadata collector diagrama tight coupling

Our module takes care of fetching, processing, and exporting the data. However, it’s a lousy design. This design infringes on the single responsibility principle. So, to improve our first design, we need to separate the concerns. After this change, our design looks like this:

我们的模块负责获取、处理和导出数据。然而,这是个糟糕的设计。这种设计违反了单一责任原则。因此,为了改进我们的第一个设计,我们需要将这些关注点分开。在这个改变之后,我们的设计看起来像这样。

Metadata collector diagrama separetion of concerns

Now, our design is decoupled into two new modules, XML Fetch and Export CSV. Meanwhile, the Metadata Collector Module depends on both. This design is better than our initial approach, but it’s still a work in progress. In the following sections, we’ll notice how we can improve our design based on good coupling practices.

现在,我们的设计被解耦为两个新模块:XML FetchExport CSV。同时,元数据采集器模块依赖于这两个模块。这种设计比我们最初的方法要好,但它仍然是一项正在进行的工作。在下面的章节中,我们将注意到如何基于良好的耦合实践来改进我们的设计。

3. Tight Coupling

3.紧密的联接

When a group of classes is highly dependent on each other, or we’ve classes that assume a lot of responsibilities is called tight coupling. Another scenario is when an object creates another object for its usage. Tight coupling code is hard to maintain.

当一组类彼此高度依赖,或者我们有承担很多责任的类时,就叫做紧耦合。另一种情况是当一个对象为其使用而创建另一个对象。紧密耦合的代码是很难维护的。

So, let us see this behavior using our base application. Let’s jump into some code definitions. First, our XMLFetch class:

所以,让我们使用我们的基本应用程序来看看这种行为。让我们跳入一些代码定义。首先,我们的XMLFetch类。

public class XMLFetch {
    public List<Object> fetchMetadata() {
        List<Object> metadata = new ArrayList<>();
        // Do some stuff
        return metadata;
    }
}

Next, the CSVExport class:

接下来是CSVExport类。

public class CSVExport {
    public File export(List<Object> metadata) {
        System.out.println("Exporting data...");
        // Export Metadata
        File outputCSV = null;
        return outputCSV;
    }
}

And finally, our MetadataCollector class:

最后,我们的MetadataCollector类。

public class MetadataCollector {
    private XMLFetch xmlFetch = new XMLFetch();
    private CSVExport csvExport = new CSVExport();
    public void collectMetadata() {
        List<Object> metadata = xmlFetch.fetchMetadata();
        csvExport.export(metadata);
    }
}

As we can notice, our MetadataCollector class depends on XMLFecth and CSVExport classes. Additionally, it is in charge of creating them.

我们可以注意到,我们的MetadataCollector类依赖于XMLFecthCSVExport类。此外,它还负责创建它们。

If we need to improve our collector, maybe add a new data JSON fetcher and export data in PDF format, we need to include these new elements inside our class. Let’s code the new “improved” class:

如果我们需要改进我们的收集器,也许添加一个新的数据JSON获取器,并以PDF格式导出数据,我们需要在我们的类中包含这些新元素。让我们对新的 “改进 “类进行编码。

public class MetadataCollector {
    ...
    private CSVExport csvExport = new CSVExport();
    private PDFExport pdfExport = new PDFExport();
    public void collectMetadata(int inputType, int outputType) {
        if (outputType == 1) {
            List<Object> metadata = null;
            if (inputType == 1) {
                metadata = xmlFetch.fetchMetadata();
            } else {
                metadata = jsonFetch.fetchMetadata();
            }
            csvExport.export(metadata);
        } else {
            List<Object> metadata = null;
            if (inputType == 1) {
                metadata = xmlFetch.fetchMetadata();
            } else {
                metadata = jsonFetch.fetchMetadata();
            }
            pdfExport.export(metadata);
        }
    }
}

The Metadata Collector Module needs some flags to handle new functionalities. Based on the flag value, the respective child module will be instantiated. However, every new functionality not only makes our code more complex but also makes maintenance harder. This is a sign of tight coupling, and we must avoid it.

元数据收集器模块需要一些标志来处理新功能。根据标志值,相应的子模块将被实例化。然而,每一个新的功能不仅使我们的代码更加复杂,而且也使维护更加困难。这是紧耦合的标志,我们必须避免它。

4. Loose Coupling

4.松动的联接

During the development process, the number of relationships among all our classes needs to be the fewest possible. This is called loose coupling. Loose coupling is when an object gets the object to be used from external sources. Our objects are independent one each other. Loosely coupled code reduces maintenance effort. Moreover, it provides much more flexibility to the system.

在开发过程中,我们所有的类之间的关系数量需要尽可能的少。这就是所谓的松耦合。松散耦合是指一个对象从外部获得要使用的对象。我们的对象是相互独立的。松散耦合的代码减少了维护工作。此外,它为系统提供了更大的灵活性。

Loose coupling is expressed through the Dependency Inversion Principle. In the next section, we will describe it.

松散耦合是通过依赖反转原则来表达的。在下一节中,我们将描述它。

5. Dependency Inversion Principle

5.依赖性反转原则

The Dependency Inversion Principle (DIP) refers to how high levels modules should not depend on low-level modules for their responsibilities. Both should depend on abstraction.

依赖反转原则(DIP)指的是高层模块不应该依赖低层模块来承担其职责两者都应该依赖抽象

In our design, this is the fundamental problem. The metadata collector (high-level) module depends on fetch XML and export CSV data(low-level) modules.

在我们的设计中,这是一个基本问题。元数据收集器(高层)模块依赖于获取XML和导出CSV数据(低层)模块。

But what can we do to improve our design? DIP shows us the solution, but it doesn’t talk about how to implement it. In this case, it is when Inversion of Control(IoC) takes action. IoC indicates the way of defining abstractions between our modules. To sum up, it is the way of implementing DIP.

但我们能做些什么来改进我们的设计呢?DIP向我们展示了解决方案,但它并没有谈到如何实现它。在这种情况下,就是控制反转(IoC)发挥作用的时候了。IoC指出了在我们的模块之间定义抽象的方式。总而言之,它是实现DIP的方法。

So, let’s apply DIP and IoC to our current example. First, we need to define an interface for fetching and other for exporting data. Let’s jump into code to see how to do that:

所以,让我们把DIP和IoC应用到我们目前的例子中。首先,我们需要定义一个用于获取和输出数据的接口。让我们跳到代码中看看如何做到这一点。

public interface FetchMetadata {
    List<Object> fetchMetadata();
}

Simple, isn’t it? Now we define our exporting interface:

很简单,不是吗?现在我们定义我们的输出接口。

public interface ExportMetadata {
    File export(List<Object> metadata);
}

Moreover, we need to implement these interfaces in the correspondent class. In short, we need to update our current classes:

此外,我们还需要在相应的类中实现这些接口。简而言之,我们需要更新我们当前的类。

public class XMLFetch implements FetchMetadata {
    @Override
    public List<Object> fetchMetadata() {
        List<Object> metadata = new ArrayList<>();
        // Do some stuff
        return metadata;
    }
}

Next, we need to update the CSVExport class:

接下来,我们需要更新CSVExport类。

public class CSVExport implements ExportMetadata {
    @Override
    public File export(List<Object> metadata) {
        System.out.println("Exporting data...");
        // Export Metadata
        File outputCSV = null;
        return outputCSV;
    }
}

Additionally, we update the main module’s code to support new design changes. Let’s see how it looks:

此外,我们更新了主模块的代码,以支持新的设计变化。让我们看看它看起来如何。

public class MetadataCollector {
    private FetchMetadata fetchMetadata;
    private ExportMetadata exportMetadata;
    public MetadataCollector(FetchMetadata fetchMetadata, ExportMetadata exportMetadata) {
        this.fetchMetadata = fetchMetadata;
        this.exportMetadata = exportMetadata;
    }
    public void collectMetadata() {
        List<Object> metadata = fetchMetadata.fetchMetadata();
        exportMetadata.export(metadata);
    }
}

We can observe two main changes in our code. First, the class depends only on abstractions, not concrete types. On the other hand, we remove the dependency on low-level modules. There is no need to keep any logic related to low-level module creation in the collector module. The interaction with those modules is through a standard interface. The advantage of this design is that we can add new modules to fetch and export data, and our collector code won’t change.

我们可以观察到我们代码中的两个主要变化。首先,这个类只依赖于抽象,而不是具体类型。另一方面,我们消除了对低级模块的依赖。没有必要在收集器模块中保留任何与低级模块创建有关的逻辑。与这些模块的交互是通过一个标准的接口。这种设计的好处是,我们可以添加新的模块来获取和导出数据,而我们的收集器代码不会改变。

By applying DIP and IoC, we improve our system design. By inverting (changing) the control, the application becomes decoupled, testable, extensible, and maintainable. The following image shows us how looks the current design:

通过应用DIP和IoC,我们改进了我们的系统设计。通过反转(改变)控制,应用程序变得解耦、可测试、可扩展和可维护。下面的图片向我们展示了目前的设计情况。

Metadata collector diagrama using DIP and IoCFinally, we remove any tightly coupled code from our code base and enhance the initial design with loosely coupled code using DIP with IoC.

Metadata collector diagrama using DIP and IoC最后,我们从代码库中删除任何紧耦合的代码,并用松耦合的代码使用DIP与IoC增强初始设计。

6. Conclusion

6.结语

In this article, we covered coupling in Java. We first went through the general coupling definition. Then, we observed the differences between tightly coupling and loosely coupling. Later, we learned to apply DIP with IoC to get a loosely coupled code. This walk-through was done by following an example design. We could observe how our code improved in each step by applying good design patterns.

在这篇文章中,我们介绍了Java中的耦合。我们首先浏览了一般的耦合定义。然后,我们观察了紧耦合和松耦合的区别。后来,我们学习了如何应用DIP和IoC来获得松散耦合的代码。这个演练是通过一个设计实例完成的。我们可以观察到我们的代码是如何通过应用好的设计模式在每个步骤中得到改进的。

As usual, our code is available over on GitHub.

像往常一样,我们的代码可以在GitHub上下载