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

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

相关文章

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

Python中re模块结合正则表达式的实际应用案例

《Python中re模块结合正则表达式的实际应用案例》Python中的re模块是用于处理正则表达式的强大工具,正则表达式是一种用来匹配字符串的模式,它可以在文本中搜索和匹配特定的字符串模式,这篇文章主... 目录前言re模块常用函数一、查看文本中是否包含 A 或 B 字符串二、替换多个关键词为统一格式三、提

Java MQTT实战应用

《JavaMQTT实战应用》本文详解MQTT协议,涵盖其发布/订阅机制、低功耗高效特性、三种服务质量等级(QoS0/1/2),以及客户端、代理、主题的核心概念,最后提供Linux部署教程、Sprin... 目录一、MQTT协议二、MQTT优点三、三种服务质量等级四、客户端、代理、主题1. 客户端(Clien

CSS中的Static、Relative、Absolute、Fixed、Sticky的应用与详细对比

《CSS中的Static、Relative、Absolute、Fixed、Sticky的应用与详细对比》CSS中的position属性用于控制元素的定位方式,不同的定位方式会影响元素在页面中的布... css 中的 position 属性用于控制元素的定位方式,不同的定位方式会影响元素在页面中的布局和层叠关

SpringBoot3应用中集成和使用Spring Retry的实践记录

《SpringBoot3应用中集成和使用SpringRetry的实践记录》SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故... 目录1. 简介2. 环境准备3. 使用方式3.1 注解方式 基础使用自定义重试策略失败恢复机制注意事项

Python使用Tkinter打造一个完整的桌面应用

《Python使用Tkinter打造一个完整的桌面应用》在Python生态中,Tkinter就像一把瑞士军刀,它没有花哨的特效,却能快速搭建出实用的图形界面,作为Python自带的标准库,无需安装即可... 目录一、界面搭建:像搭积木一样组合控件二、菜单系统:给应用装上“控制中枢”三、事件驱动:让界面“活”

如何确定哪些软件是Mac系统自带的? Mac系统内置应用查看技巧

《如何确定哪些软件是Mac系统自带的?Mac系统内置应用查看技巧》如何确定哪些软件是Mac系统自带的?mac系统中有很多自带的应用,想要看看哪些是系统自带,该怎么查看呢?下面我们就来看看Mac系统内... 在MAC电脑上,可以使用以下方法来确定哪些软件是系统自带的:1.应用程序文件夹打开应用程序文件夹

Python Flask 库及应用场景

《PythonFlask库及应用场景》Flask是Python生态中​轻量级且高度灵活的Web开发框架,基于WerkzeugWSGI工具库和Jinja2模板引擎构建,下面给大家介绍PythonFl... 目录一、Flask 库简介二、核心组件与架构三、常用函数与核心操作 ​1. 基础应用搭建​2. 路由与参

Spring Boot中的YML配置列表及应用小结

《SpringBoot中的YML配置列表及应用小结》在SpringBoot中使用YAML进行列表的配置不仅简洁明了,还能提高代码的可读性和可维护性,:本文主要介绍SpringBoot中的YML配... 目录YAML列表的基础语法在Spring Boot中的应用从YAML读取列表列表中的复杂对象其他注意事项总