强内聚 弱耦合
强内聚 弱耦合
内聚(Cohesion)是一个模块内部各成分之间相关联程度的度量。耦合(Coupling)是模块之间依赖程度的度量。内聚和耦合是密切相关的,与其它模块存在强耦合的模块通常意味着弱内聚,而强内聚的模块通常意味着与其它模块之间存在弱耦合。模块设计追求强内聚,弱耦合。
一、内聚强度
内聚按强度从低到高有以下几种类型:
(1) 偶然内聚。如果一个模块的各成分之间毫无关系,则称为偶然内聚。
(2) 逻辑内聚。几个逻辑上相关的功能被放在同一模块中,则称为逻辑内聚。如一个模块读取各种不同类型外设的输入。尽管逻辑内聚比偶然内聚合理一些,但逻辑内聚的模块各成分在功能上并无关系,即使局部功能的修改有时也会影响全局,因此这类模块的修改也比较困难。
(3) 时间内聚。如果一个模块完成的功能必须在同一时间内执行(如系统初始化),但这些功能只是因为时间因素关联在一起,则称为时间内聚。
(4) 过程内聚。如果一个模块内部的处理成分是相关的,而且这些处理必须以特定的次序执行,则称为过程内聚。
(5) 通信内聚。如果一个模块的所有成分都操作同一数据集或生成同一数据集,则称为通信内聚。
(6) 顺序内聚。如果一个模块的各个成分和同一个功能密切相关,而且一个成分的输出作为另一个成分的输入,则称为顺序内聚。
(7) 功能内聚。模块的所有成分对于完成单一的功能都是必须的,则称为功能内聚。
二、耦合强度
耦合的强度依赖于以下几个因素:(1)一个模块对另一个模块的调用;(2)一个模块向另一个模块传递的数据量;(3)一个模块施加到另一个模块的控制的多少;(4)模块之间接口的复杂程度。
耦合按从强到弱的顺序可分为以下几种类型:
(1)内容耦合。当一个模块直接修改或操作另一个模块的数据,或者直接转入另一个模块时,就发生了内容耦合。此时,被修改的模块完全依赖于修改它的模块。
(2)公共耦合。两个以上的模块共同引用一个全局数据项就称为公共耦合。
(3)控制耦合。一个模块在界面上传递一个信号(如开关值、标志量等)控制另一个模块,接收信号的模块的动作根据信号值进行调整,称为控制耦合。
(4)标记耦合。模块间通过参数传递复杂的内部数据结构,称为标记耦合。此数据结构的变化将使相关的模块发生变化。
(5)数据耦合。模块间通过参数传递基本类型的数据,称为数据耦合。
(6)非直接耦合。模块间没有信息传递时,属于非直接耦合。
如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,坚决避免使用内容耦合。
举例:
3.1 耦合类型
耦合主要分为以下几类,耦合度从低到高,分别是:
非直接耦合 < 数据耦合 < 标记耦合 < 控制耦合 < 外部耦合 < 公共耦合 < 内容耦合
非直接耦合(Nondirect coupling)
A/B两个模块之间没有任何关系,不会调用对方的API,不依赖对方的结构体、宏……不用包含头文件,删除一个模块对另一个模块没有任何影响,它们之间的联系完全是通过主模块的控制和调用来实现的,这种耦合关系称之为 非直接耦合。
例子:
//A模块
int Add(int num1,int num2)
{
return (num1+num2);
}
//B模块
typedef struct _SUB_PARA_
{
int num1;
int num2;
}SubPara;
int Sub(SubPara* pNum)
{
return (pNum->num1-pNum->num2);
}
数据耦合(Data Coupling)
数据耦合指的是,两个模块调用时,传递的是简单的数据值,不是数据结构或者其他复杂变量。
如果一个模块访问另一个模块时,彼此之间是通过数据参数(不是控制参数、公共数据结构或外部变量)来交换输入、输出信息的,则称这种耦合为数据耦合。。在软件程序结构中至少必须有这类耦合。
在上例子中,A/B之间没有相互的依赖,但是可能A、B的API都会被C模块调用,(个人意见:我们应该把C模块划分到高一层去)。
C调用A,用的是参数传入,传递的都是int型的入参,即为C和A为数据耦合。
例子:
//C模块
int Sum(int num1 , int num2 ,int num3,int 4)
{
int res1 = Add(num1,num2);
int res2 = Add(num3,num4);
return Add(res1,res2);
}
标记耦合(Stamp Coupling)
如果在调用过程中,传递的不是普通参数,而是一个结构体,这个结构体是属于某个模块的,而不是简单变量。
如果一组模块通过参数表传递记录信息,就是标记耦合。事实上,这组模块共享了这个记录,它是某一数据结构的子结构,而不是简单变量。这要求这些模块都必须清楚该记录的结构,并按结构要求对此记录进行操作。在设计中应尽量避免这种耦合,它使在数据结构上的操作复杂化了。
原则上,应该尽量把使用同一结构体的操作尽量集中在一个模块中,消除这种耦合。
还是这个例子。
C调用B,用的是结构体传入,即为标记耦合
//C模块第二部分
int Sub2(int num1, int num2)
{
SubPara Temp;
Temp.num1 = num1;
Temp.num2 = num2;
return Sub(&Temp);
}
控制耦合
如果一个模块通过传送开关、标志、名字等控制信息,明显地控制选择另一模块的功能,就是控制耦合。
偷一张别人的图,看的很明白:
在这里插入图片描述
不论这个Flag是怎么传过来的,在功能逻辑上,B模块是依赖于A模块的。
外部耦合(External Coupling)
一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
举个例子,DE模块都直访问了一个全局变量,而不是通过入参传入。
//D模块
int g_Para = 1;
int Func1()
{
return (g_Para+1);
}
//E模块
extern int g_Para;
int Func2()
{
return (g_Para+2);
}
公共耦合
若一组模块都访问同一个公共数据环境,则他们之间的耦合就称为公共耦合。公共的数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。
类似外部耦合,只不过依赖不再是简单变量。
内容耦合
又称病态耦合。如果发生下列情形,两个模块之间就发生了内容耦合。
一个模块直接访问另一个模块的内部数据;
一个模块不通过正常入口转到另一模块内部;
两个模块有一部分程序代码重叠(只可能出现在汇编语言中);
一个模块有多个入口。
在内容耦合的情形,所访问模块的任何变更,或者用不同的编译器对它再编译,都会造成程序出错。好在大多数高级程序设计语言已经设计成不允许出现内容耦合。它一般出现在汇编语言程序中。这种耦合是模块独立性最弱的耦合。
3.2 如何解耦
依赖注入、命名查找
4. 总结
我的观点是:
高内聚,低耦合肯定是我们的追求,但并不是内聚越高,耦合越低就一定好,并非要是使用很多兜圈子的代码来降低耦合,我们应该允许单向调用。
项目设计应该先做分层设计。
同层级的模块间,尽量保持相互独立,不相互调用。如果无法避免,尽量把被高度依赖的部分封装到同一个模块中,这个模块作为必选功能模块,不可移除
上层可以调用下层模块的API和结构体。调用多使用传参,传参多用普通类型
每个模块维护自己的内部变量和状态,如果变量少就用基本数据类型,多就用结构体,与外界任何交互都有标准的输入输出接口。
————————————————
版权声明:本文为CSDN博主「taotao830」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tao475824827/article/details/104157419/
引用地址:https://www.cnblogs.com/gaoxue/archive/2011/12/27/2303699.html