前言
高内聚低耦合,是软件工程中的概念,是判断软件设计好坏的标准,主要用于程序的面向对象的设计,主要看类的内聚性是否高,耦合度是否低。目的是使程序模块的可重用性、移植性大大增强。
内聚性
(Cohesion)又称块内聚力。指模块的功能强度的度量,即一个模块内部各个元素彼此结合的紧密程度的度量。若一个模块内各元素(语名之间、程序段之间)联系的越紧密,则它的内聚性就越高。
所谓高内聚是指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。
增强内聚性的方法:
模块只对外暴露最小限度的接口,形成最低的依赖关系。
只要对外接口不变,模块内部的修改,就不得影响其他模块。
删除一个模块,应当只影响有依赖关系的其他模块,而不应该影响其他无关部分。
以下的情形也会降低程序的内聚性:
许多机能封装在一类型内,可以借由方法供外界使用,但机能彼此类似之处不多。
在方法中进行许多不同的机能,使用的是相关性低或不相关的数据。
低内聚性的缺点如下:
增加理解模块的困难度。
增加维护系统的困难度,因为一个逻辑修改会影响许多模块,而一个模块的修改会使得一些相关模块也要修改。
增加模块复用困难度,因为大部分的应用程序无法复用一个由许多不一定相关的机能组成的模块。
内聚的种类
内聚性是一种非量化的量测,可利用评量规准来确认待确认源代码的内聚性的分类。内聚性的分类如下,由低到高排列:
偶然内聚性(Coincidental cohesion,最低)。
偶然内聚性是指模块中的机能只是刚好放在一起,模块中各机能之间唯一的关系是其位置在同一个模块中(例如:“工具”模块)。
逻辑内聚性(Logical cohesion)。
逻辑内聚性是只要机能在逻辑上分为同一类,不论各机能的本质是否有很大差异,就将这些机能放在同一模块中(例如将所有的鼠标和键盘都放在输入处理副程序中)。模块内执行几个逻辑上相似的功能,通过参数确定该模块完成哪一个功能。
时间内聚性(Temporal cohesion)。
时间内聚性是指将相近时间点运行的程序,放在同一个模块中(例如在捕捉到一个异常后调用一函数,在函数中关闭已打开的文件、产生错误日志、并告知用户)。
程序内聚性(Procedural cohesion)。
程序内聚性是指依一组会按照固定顺序运行的程序放在同一个模块中(例如一个函数检查文件的权限,之后打开文件)。
联系内聚性/信息内聚/通信内聚(Communicational cohesion)。
联系内聚性是指模块中的机能因为处理相同的数据或者指各处理使用相同的输入数据或者产生相同的输出数据,所以放在同一个模块中(例如一个模块中的许多机能都访问同一个记录)。
依序内聚性/顺序内聚(Sequential cohesion)。
依序内聚性是指模块中的各机能彼此的输入及输出数据相关,一模块的输出数据是另一个模块的输入,类似工厂的生产线(例如一个模块先读取文件中的数据,之后再处理数据)。
功能内聚性(Functional cohesion,最高)。
功能内聚性是指模块中的各机能是因为它们都对模块中单一明确定义的任务有贡献。
耦合性
耦合性(Coupling,dependency,或称耦合力或耦合度)是一种软件度量,是指一程序中,模块及模块之间信息或参数依赖的程度。
耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。
耦合性的分类
耦合性可以是低耦合性(或称为松散耦合),也可以是高耦合性(或称为紧密耦合)。以下列出一些耦合性的分类,从高到低依序排列:
内容耦合:(content coupling,耦合度最高),一个模块直接访问另一模块的内容,则称这两个模块为内容耦合。若在程序中出现下列情况之一,则说明两个模块之间发生了内容耦合:
1. 一个模块直接访问另一个模块的内部数据。
2. 一个模块不通过正常入口而直接转入到另一个模块的内部。
3. 两个模块有一部分代码重叠(该部分代码具有一定的独立功能)。
4. 一个模块有多个入口。病态耦合(pathological coupling)当一个模块直接使用另一个模块的内部数据,或通过非正常入口而转入另一个模块内部。
共享耦合/公共耦合(common coupling):一组模块都访问同一个全局数据结构,则称之为公共耦合。公共数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。如果模块只是向公共数据环境输入数据,或是只从公共数据环境取出数据,这属于比较松散的公共耦合;如果模块既向公共数据环境输入数据又从公共数据环境取出数据,这属于较紧密的公共耦合。
公共耦合会引起以下问题:1. 无法控制各个模块对公共数据的存取,严重影响了软件模块的可靠性和适应性。
2. 使软件的可维护性变差。若一个模块修改了公共数据,则会影响相关模块。
3. 降低了软件的可理解性。不容易清楚知道哪些数据被哪些模块所共享,排错困难。一般地,仅当模块间共享的数据很多且通过参数传递很不方便时,才使用公共耦合。
也称为全局耦合(global coupling.)指通过一个公共数据环境相互作用的那些模块间的耦合。公共耦合的复杂程序随耦合模块的个数增加而增加。
外部耦合(external coupling):一组模块都访问同一全局简单变量,而且不通过参数表传递该全局变量的信息,则称之为外部耦合。
控制耦合(control coupling):模块之间传递的不是数据信息,而是控制信息例如标志、开关量等,一个模块控制了另一个模块的功能。
特征耦合/标记耦合(stamp coupling):调用模块和被调用模块之间传递数据结构而不是简单数据,同时也称作特征耦合。表就和的模块间传递的不是简单变量,而是像高级语言中的数据名、记录名和文件名等数据结果,这些名字即为标记,其实传递的是地址。
数据耦合/数据耦合(data coupling):调用模块和被调用模块之间只传递简单的数据项参数。相当于高级语言中的值传递。
消息耦合(message coupling,是无耦合之外,耦合度最低的耦合)
可以借由以下二个方式达成:状态的去中心化(例如在对象中),组件间利用传入值或消息传递 (计算器科学)来通信。
无耦合:模块完全不和其他模块交换信息。
降低耦合度的方法
少使用类的继承,多用接口隐藏实现的细节。 Java面向对象编程引入接口除了支持多态外, 隐藏实现细节也是其中一个目的。
模块的功能化分尽可能的单一,道理也很简单,功能单一的模块供其它模块调用的机会就少。(其实这是高内聚的一种说法,高内聚低耦合一般同时出现)。
遵循一个定义只在一个地方出现。
少使用全局变量。
类属性和方法的声明少用public,多用private关键字。
多用设计模式,比如采用MVC的设计模式就可以降低界面与业务逻辑的耦合度。
尽量不用“硬编码”的方式写程序,同时也尽量避免直接用SQL语句操作数据库。
最后当然就是避免直接操作或调用其它模块或类(内容耦合);如果模块间必须存在耦合,原则上尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,避免使用内容耦合。
紧密耦合的系统在开发阶段有以下的缺点:
- 一个模块的修改会产生涟漪效益,其他模块也需随之修改。
- 由于模块之间的相依性,模块的组合会需要更多的精力及时间。
- 由于一个模块有许多的相依模块,模块的可复用性低。
分析
高内聚,低耦合的系统有什么好处呢?
事实上,短期来看,并没有很明显的好处,甚至短期内会影响系统的开发进度,因为高内聚,低耦合的系统对开发设计人员提出了更高的要求。
高内聚,低耦合的好处体现在系统持续发展的过程中,高内聚,低耦合的系统具有更好的重用性,维护性,扩展性,可以更高效的完成系统的维护开发,持续的支持业务的发展,而不会成为业务发展的障碍。
是否意味着内聚越高,耦合越低越好?
真正好的设计是在高内聚和低耦合间进行平衡,也就是说高内聚和低耦合是冲突的。
最强的内聚莫过于一个类只写一个函数,这样内聚性绝对是最高的。但这会带来一个明显的问题:类的数量急剧增多,这样就导致了其它类的耦合特别多,于是整个设计就变成了“高内聚高耦合”了。由于高耦合,整个系统变动同样非常频繁。
对于耦合来说,最弱的耦合是一个类将所有的函数都包含了,这样类完全不依赖其它类,耦合性是最低的。但这样会带来一个明显的问题:内聚性很低,于是整个设计就变成了“低耦合低内聚”了。由于低内聚,整个类的变动同样非常频繁。
真正做到高内聚、低耦合是很难的,很多时候未必一定要这样,更多的时候“最适合”的才是最好的,不过,审时度势、融会贯通、人尽其才、物尽其用,才是设计的王道。
软件设计时,如何做好高内聚低耦合?
在模块划分时,要遵循“一个模块,一个功能”的原则,尽可能使模块达到功能内聚。
在设计上我们应采用以下原则:若模块间必须存在耦合,应尽量使用数据耦合,少用控制耦合,慎用或有控制地使用公共耦合,并限制公共耦合的范围,尽量避免内容耦合。