【架构设计】如何让你的应用做到高内聚、低耦合?

2024-01-13 23:20

本文主要是介绍【架构设计】如何让你的应用做到高内聚、低耦合?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

最近review公司的代码,发现代码耦合程度特别高,修改一处,不知不觉就把其他地方影响到了,这就让我思考该如何让我们写的代码足够内聚,减少耦合呢?

"高内聚、松耦合"是一个非常重要的设计思想,能够有效地提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。它可以用来指导不同粒度代码的设计与开发,比如系统、模块、类,甚至是函数,也可以应用到不同的开发场景中,比如微服务、框架、组件、类库等。本文我们来探讨下如何让我们的应用做到高内聚、低耦合。

什么是高内聚?

首先我们将目光投射到内聚上,通常我们的代码的内聚归为7类,如下图所示,内聚性从高到低。

  1. 功能内聚

将相同功能放到一个类或者模块中,内聚程度最高。

  1. 顺序内聚

如果一个功能的输出是另外一个功能的输入,这种存在顺序依赖关系的,我们将他们归到一个模块中叫做顺序内聚。

  1. 通信内聚

如果功能点使用相同输入或输出数据,我们将他们内聚到一个模块中,叫做通信内聚。

  1. 过程内聚

如果不同的功能是由同一个控制流支配的,我们称作过程内聚。

  1. 时间内聚

不同的功能在同一时间段内执行,比如银行不同的跑批任务,由时间去控制是否放在同一个模块中的内聚叫做时间内聚。

  1. 逻辑内聚

不同的功能可能他们内部的逻辑是一致的,我们将他归在一起叫做逻辑内聚。

  1. 偶然内聚

而偶然内聚是指不同的功能,没什么关联就直接放在一起了。这种情况很常见,比如A团队同时承担了支付、物流、产品等功能的开发,为了节省开发资源,他们就把不相干的功能放到一个模块中,那么这种偶然内聚会随着业务发展遇到各种问题。

上面讲解了内聚的可能7种分类,大家不用太较真,了解一下就行。

小结一下,我们所说的高内聚,其实是用来指导类或者模块本身的设计,简单来说,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中,代码容易维护。

什么是低耦合?

现在我们聚焦到耦合上,耦合实际上关注在类与类之间或者模块与模块之间依赖关系的设计,我们通常有下面7种类型的耦合关系。

  1. 非直接耦合

两个模块之间没有直接关系,模块独立性最强

  1. 数据耦合

两个模块之间用参数传递关联,模块之间影响最小的耦合关系。比如订单系统输入物流编号ID,物流系统返回你具体的物流信息。

  1. 标记耦合

两个模块依赖同样的一个数据结构,传递的是数据结构。

比如租房费用计算系统,需要计算水费和电费,如果你传用户信息这个数据结构,让水费和电费系统领取用户对象的用水量和用电量,这就是标记耦合。更好的做法应该只需要传必要的用水量和用电量,而不是传输整个用户对象。

  1. 控制耦合

两个模块之间传输控制信息,比如某个标志或者开关,调用模块需要知道被调用模块的内部逻辑,增加了相互依赖和理解的复杂度。

  1. 外部耦合

一组模块需要与外部环境关联,这组模块访问同一全局变量,外部耦合有时候必不可少,但应尽量减少此类模块数量。

  1. 公共耦合

一组模块均访问同一全局数据区,这种情况叫做公共耦合。比如有一个公共变量,不同模块都去修改它。

  1. 内容耦合

一个模块直接操作或修改另外一个模块的内部书,一个模块不通过正常入口访问另外一个模块,这就是内容耦合,是最糟糕的情况,需要避免。比如直接调用另外一个类的set方法,所以这就要求我们不要无脑的把类的任何属性都加上set方法。

上面大致分享了耦合的7种情况,你们项目属于哪种情况呢?

这边再总结一下, 所谓低耦合是说,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动不会或者很少导致依赖类的代码改动。

高内聚,低耦合有什么关系?

前面讲解了内聚和耦合的多种情况,那么高内聚、低耦合之间有什么关系呢?

上图中左边部分的代码设计中,类的粒度比较小,每个类的职责都比较单一。相近的功能都放到了一个类中,不相近的功能被分割到了多个类中。这样类更加独立,代码的内聚性更好。一个类的修改,只会影响到一个依赖类的代码改动。我们只需要测试这一个依赖类是否还能正常工作就行了。

上图中右边部分的代码设计中,类粒度比较大,低内聚,功能大而全,不相近的功能放到了一个类中。这就导致很多其他类都依赖这个类。当我们修改这个类的某一个功能代码的时候,会影响依赖它的多个类。我们需要测试这三个依赖类,是否还能正常工作。

所以说, “高内聚”有助于“松耦合”,同理,“低内聚”也会导致“紧耦合”

如何做到高内聚、低耦合呢?

关于如何做到模块或者类的高内聚、低耦合,有一个很经典的指导法则,那就是迪米特法则

迪米特法则是说不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。换更形象点的说,每个模块只和自己的朋友“说话”(talk),不和陌生人“说话”(talk)。

展开来讲,我们在做设计的时候特别关注下面的一些要点。

  1. 多用接口隐藏实现的细节, 接口是一个契约,相对稳定。
  2. 尽量避免不同模块或者类之间共享全局变量,万一一个地方需要修改,连带的其他模块也会影响
  3. 能用private的地方,坚决不用public, 不要给类的任何属性都加set方法,体现良好的封装性,暴露越少,影响也就越少。
  4. 合理使用设计模式,因为设计模式会很好的保证代码的可扩展性、封装性
  5. 注意分层设计和调用,比如在业务层直接用SQL语句操作数据库,而应该讲数据库操作封装到DAO层中,业务层调用DAO层
  6. 避免直接操作或者调用其他模块或类(内容耦合),也就是直接调用set方法,而是应该通过接口调用
  7. 尽量使用数据耦合,少用控制耦合。比如传输一个普通的数据变量是合适的。但传输一个控制命令给你,让你去执行分支a还是分支b还是分支c,这就不是很合适,所以尽量采用一些其他的方法来规避掉这些控制命令的发送。
  8. 模块的功能划分尽可能的单一, 遵循单一职责原则
  9. 模块只堆外暴露最小限度的接口,遵循接口隔离原则
  10. 模块之间的交互要尽量少,接口的设计要尽量简单,比如能传基本类类型就不要传输整个对象。

总结

“高内聚、松耦合”是一个非常重要的设计思想,能够有效提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。“高内聚”用来指导类本身的设计,“松耦合”用来指导类与类之间依赖关系的设计。

所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中。所谓松耦合指的是,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动也不会或者很少导致依赖类的代码改动。

所以我们在设计的时候,尽量要多动动脑子,想想这个功能是不是属于这个模块的呢?他们之间的交互合理吗?复杂吗?有没有更简单的方式呢?多问自己几个问题,你做出的设计会越来越优秀。

这篇关于【架构设计】如何让你的应用做到高内聚、低耦合?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/603136

相关文章

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

C语言中位操作的实际应用举例

《C语言中位操作的实际应用举例》:本文主要介绍C语言中位操作的实际应用,总结了位操作的使用场景,并指出了需要注意的问题,如可读性、平台依赖性和溢出风险,文中通过代码介绍的非常详细,需要的朋友可以参... 目录1. 嵌入式系统与硬件寄存器操作2. 网络协议解析3. 图像处理与颜色编码4. 高效处理布尔标志集合

Java中的Lambda表达式及其应用小结

《Java中的Lambda表达式及其应用小结》Java中的Lambda表达式是一项极具创新性的特性,它使得Java代码更加简洁和高效,尤其是在集合操作和并行处理方面,:本文主要介绍Java中的La... 目录前言1. 什么是Lambda表达式?2. Lambda表达式的基本语法例子1:最简单的Lambda表

Python结合PyWebView库打造跨平台桌面应用

《Python结合PyWebView库打造跨平台桌面应用》随着Web技术的发展,将HTML/CSS/JavaScript与Python结合构建桌面应用成为可能,本文将系统讲解如何使用PyWebView... 目录一、技术原理与优势分析1.1 架构原理1.2 核心优势二、开发环境搭建2.1 安装依赖2.2 验

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

SpringShell命令行之交互式Shell应用开发方式

《SpringShell命令行之交互式Shell应用开发方式》本文将深入探讨SpringShell的核心特性、实现方式及应用场景,帮助开发者掌握这一强大工具,具有很好的参考价值,希望对大家有所帮助,如... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定

SpringBoot应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F

MySQL 分区与分库分表策略应用小结

《MySQL分区与分库分表策略应用小结》在大数据量、复杂查询和高并发的应用场景下,单一数据库往往难以满足性能和扩展性的要求,本文将详细介绍这两种策略的基本概念、实现方法及优缺点,并通过实际案例展示如... 目录mysql 分区与分库分表策略1. 数据库水平拆分的背景2. MySQL 分区策略2.1 分区概念

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

C语言函数递归实际应用举例详解

《C语言函数递归实际应用举例详解》程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用,:本文主要介绍C语言函数递归实际应用举例的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录前言一、递归的概念与思想二、递归的限制条件 三、递归的实际应用举例(一)求 n 的阶乘(二)顺序打印