Java中的耦合

2023/05/26

1. 简介

在本教程中,我们将学习Java中的耦合,介绍耦合的类型和对每种类型的描述。最后,我们简要描述依赖倒置原则和控制反转以及它们与耦合的关系。

2. Java中的耦合

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

考虑以下场景:我们正在设计一个元数据收集器应用程序,此应用程序为我们收集元数据。它以XML格式获取元数据,然后将获取的元数据导出到CSV文件,仅此而已。正如我们在设计中所理解的那样,最初的方法可能是:

我们的模块负责获取、处理和导出数据。然而,这是一个糟糕的设计,这种设计违反了单一职责原则。因此,为了改进我们的第一个设计,我们需要分离关注点。在此更改之后,我们的设计如下所示:

现在,我们的设计被解耦为两个新模块,XML Fetch和Export CSV,同时,元数据收集器模块依赖于两者。这种设计比我们最初的方法要好,但它仍在最佳实践的路上。在以下各节中,我们将介绍如何根据良好的耦合实践改进设计。

3. 紧密耦合

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

因此,让我们使用我们的基础应用程序来查看此行为。首先,定义我们的XMLFetch类:

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

接下来是CSVExport类:

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

最后是我们的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);
    }
}

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

如果我们需要改进我们的收集器,可能会添加一个新的数据JSON提取器并以PDF格式导出数据,我们需要在我们的类中包含这些新元素,因此“改进”后的MetadataCollector类可能如下所示:

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);
        }
    }
}

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

4. 松散耦合

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

松散耦合通过依赖倒置原则来表达。在下一节中,我们将对其进行描述。

5. 依赖倒置原则

依赖倒置原则(DIP)指的是高层模块不应依赖于低层模块来履行其职责,两者都应该依赖于抽象

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

但是我们可以做些什么来改进我们的设计呢?DIP向我们展示了解决方案,但它没有谈论如何实现它。在这种情况下,它是控制反转(IoC)采取行动的时候。IoC表示在我们的模块之间定义抽象的方式,总结起来就是DIP的实现方式。

因此,让我们将DIP和IoC应用于我们当前的示例。首先,我们需要定义一个用于获取数据的接口和一个用于导出数据的接口。让我们看看如何通过代码做到这一点:

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

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

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

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

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

接下来,我们需要更新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;
    }
}

此外,我们更新了主模块的代码以支持新的设计更改,下面是更改后的MetadataCollector类:

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);
    }
}

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

通过应用DIP和IoC,我们改进了系统设计。通过反转(改变)控制,应用程序变得解耦、可测试、可扩展和可维护。下图是当前设计的描述:

6. 总结

在本文中,我们介绍了Java中的耦合。首先我们了解了一般的耦合定义,然后,我们介绍了紧密耦合和松散耦合的区别,并学习了将DIP与IoC一起应用以实现松散耦合的代码。本文的例子是按照示例设计完成的,通过应用良好的设计模式,我们可以观察我们的代码是如何在每一步中得到改进的。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章