前言:

低耦合高内聚,一直是软件开发人员心中始终铭记的开发准则,然而在日常的开发中,却常常忽略它的存在,或者不能清晰的知道如何去做才能更好的解耦和内聚。多年从事于软件开发工作的我,开发了很多功能,也接手过很多别人的工作,深知其中的坑与痛,所以决心做出改变,本文将从0开始逐步深入研究,探索低耦合、高内聚的编码之道。

首先,先了解下前人总结的耦合性和聚合性的分类,此部分内容来自百度,如果大家之前有所了解可以直接跳过:

耦合性简介

耦合性,也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。

一般模块之间可能的连接方式有七种,构成耦合性的七种类型。它们之间的关系为(独立性由强到弱)

非直接耦合:

如果两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的,这就是非直接耦合。这种耦合的模块独立性最强。

数据耦合:

如果一个模块访问另一个模块时,彼此之间是通过数据参数(不是控制参数、公共数据结构或外部变量)来交换输入、输出信息的,则称这种耦合为数据耦合。由于限制了只通过参数表传递数据,按数据耦合开发的程序界面简单、安全可靠。因此,数据耦合是松散的耦合,模块之间的独立性比较强。在软件程序结构中至少必须有这类耦合。

补充:数据耦合的结果只和传入的参数有关,且不会改变参数的数据,所以耦合度极低,日常开发应尽量分离出这样的模块结构来降低耦合。

标记耦合:

如果一组模块通过参数表传递记录信息,就是标记耦合,也称特征耦合。事实上,这组模块共享了这个记录,它是某一数据结构的子结构,而不是简单变量。这要求这些模块都必须清楚该记录的结构,并按结构要求对此记录进行操作。在设计中应尽量避免这种耦合,它使在数据结构上的操作复杂化了。如果采取“信息隐蔽”的方法,把在数据结构上的操作全部集中。

控制耦合:

如果一个模块通过传送开关、标志、名字等控制信息,明显地控制选择另一模块的功能,就是控制耦合。这种耦合的实质是在单一接口上选择多功能模块中的某项功能。因此,对所控制模块的任何修改,都会影响控制模块。另外,控制耦合也意味着控制模块必须知道所控制模块内部的一些逻辑关系,这些都会降低模块的独立性。

补充:日常开发中经常需要根据某种状态来决定执行的逻辑,这几乎是不可避免的,这种情况我们就需要尽量把状态逻辑封装到顶层,各个状态要执行的具体逻辑封装成独立的模块以最大限度降低影响。

外部耦合:

一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。例如C语言程序中各个模块都访问被说明为extern类型的外部变量。外部耦合引起的问题类似于公共耦合,区别在于在外部耦合中不存在依赖于一个数据结构内部各项的物理安排。

补充:外部耦合指的是共同访问一些简单变量,不涉及数据结构。

公共耦合:

若一组模块都访问同一个公共数据环境,则它们之间的耦合就称为公共耦合。公共的数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。 这种耦合会引起下列问题:

所有公共耦合模块都与某一个公共数据环境内部各项的物理安排有关,若修改某个数据的大小,将会影响到所有的模块。

无法控制各个模块对公共数据的存取,严重影响软件模块的可靠性和适应性。

公共数据名的使用,明显降低了程序的可读性。

公共耦合的复杂程度随耦合模块的个数增加而显著增加。若只是两个模块之间有公共数据环境,则公共耦合有两种情况。

若一个模块只是往公共数据环境里传送数据,而另一个模块只是从公共数据环境中取数据,则这种公共耦合叫做松散公共耦合。若两个模块都从公共数据环境中取数据,又都向公共数据环境里送数据,则这种公共耦合叫做紧密公共耦合。只有在模块之间共享的数据很多,且通过参数表传递不方便时,才使用公共耦合。否则,还是使用模块独立性比较高的数据耦合好些。

内容耦合:

如果发生下列情形,两个模块之间就发生了内容耦合。

一、一个模块直接访问另一个模块的内部数据;
二、一个模块不通过正常入口转到另一模块内部(比如反射);
三、两个模块有一部分程序代码重叠(只可能出现在汇编语言中);
四、一个模块有多个入口。

在内容耦合的情形,所访问模块的任何变更,或者用不同的编译器对它再编译,都会造成程序出错。好在大多数高级程序设计语言已经设计成不允许出现内容耦合。它一般出现在汇编语言程序中。这种耦合是模块独立性最弱的耦合。

内聚性简介

内聚的种类一般也是分成7种,从上到下内聚性依次增强:

偶然内聚:

一个模块内的各处理元素之间没有任何联系,只是偶然地被凑到一起。这种模块也称为巧合内聚,内聚程度最低(极力避免)。

逻辑内聚:

这种模块把几种相关的功能组合在一起, 每次被调用时,由传送给模块参数来确定该模块应完成哪一种功能(与控制耦合类似) 。

时间内聚:

把需要同时执行的动作组合在一起形成的模块称为时间内聚模块。

过程内聚:

构件或者操作的组合方式是,允许在调用前面的构件或操作之后,马上调用后面的构件或操作,即使两者之间没有数据进行传递。简单的说就是如果一个模块内的处理元素是相关的,而且必须以特定次序执行则称为过程内聚。例如某要完成登录的功能,前一个功能判断网络状态,后一个执行登录操作,显然是按照特定次序执行的。

通信内聚:

指模块内所有处理元素都在同一个数据结构上操作或所有处理功能都通过公用数据而发生关联(有时称之为信息内聚)。即指模块内各个组成部分都使用相同的数据结构或产生相同的数据结构。

顺序内聚:

一个模块中各个处理元素和同一个功能密切相关,而且这些处理必须顺序执行,通常前一个处理元素的输出时后一个处理元素的输入。例如某要完成获取订单信息的功能,前一个功能获取用户信息,后一个执行计算均价操作,显然该模块内两部分紧密关联。顺序内聚的内聚度比较高,但缺点是不如功能内聚易于维护。

功能内聚:

模块内所有元素的各个组成部分全部都为完成同一个功能而存在,共同完成一个单一的功能,模块已不可再分。即模块仅包括为完成某个功能所必须的所有成分,这些成分紧密联系、缺一不可。

总结

通过上面的内容,我们已经对耦合性和内聚性有了一定程度的理解,在日常的开发中,我们要尽可能用耦合性低,内聚性高的方式,参考上面的介绍。这样子在开发过程中前期会有一定的速度影响,但是在之后,无论查找问题还是扩展功能亦或是修改收益还是非常大的,当别人因为一个小的变动就抓耳挠腮,无从下手,必须重构时,你就会暗自庆幸当时的机智!

下一篇 关于解耦的研究(二)之基础解耦实践


Will
22 声望1 粉丝

专注于游戏领域中工具与性能优化方案的研究