我正在学习计算机编程,在几个地方我偶然发现了凝聚力的概念,我明白软件具有“高凝聚力”是可取的,但这意味着什么?我是一名 Java、C 和 Python 程序员,从《C++ Primer》一书中学习 C++,其中提到了内聚性,但没有在索引中包含它,你能给我指出一些关于这个主题的链接吗?我没有找到关于计算机科学凝聚力的维基百科页面,因为它只是说这是一种定性衡量标准,并没有给出真实的代码示例。
10 回答
高凝聚力是指你的班级做得很好。低凝聚力是指一个班级做了很多没有太多共同点的工作。
让我们看这个例子:
您有一个添加两个数字的类,但同一个类创建了一个显示结果的窗口。这是一个低内聚类,因为窗口和加法操作没有太多共同点。窗口是程序的可视部分,添加功能是其背后的逻辑。
要创建高内聚解决方案,您必须创建一个类 Window 和一个类 Sum。窗口会调用 Sum 的方法来获取结果并显示出来。这样,您将分别开发应用程序的逻辑和 GUI。
来自 Steve McConnell 的Code Complete的解释:
内聚是指一个类中的所有例程或例程中的所有代码支持一个中心目的的紧密程度。包含 强相关功能的类被描述为具有强内聚力,启发式目标是使内聚力尽可能强。内聚是管理复杂性的有用工具,因为类中支持中心目的的代码越多,您的大脑就越容易记住代码所做的一切。
从鲍勃叔叔的清洁代码中实现它的一些方法:
类应该有少量的实例变量。一个类的每个方法都应该操作一个或多个这些变量。一般来说,一个方法操作的变量越多,该方法对其类的凝聚力就越大。每个方法都使用每个变量的类具有最大的内聚性。
一般来说,创建这种最大凝聚力的类既不可取,也不可能;另一方面,我们希望凝聚力高。当内聚度高时,意味着类的方法和变量是相互依赖的,并且作为一个逻辑整体挂在一起。
内聚的概念与耦合的概念密切相关。此外,还有一个基于高内聚启发式的原则,称为单一责任原则(SOLID 中的 S)。
高内聚是一个软件工程概念。基本上,它说一个类应该只做它应该做的事情,并且完全做到。不要用它不应该做的函数重载它,任何与它直接相关的东西也不应该出现在其他类的代码中。
示例是相当主观的,因为我们还必须考虑规模。一个简单的程序不应该过于模块化,否则会碎片化;而复杂的程序可能需要更高级别的抽象来处理复杂性。
例如电子邮件类。它应该包含数据成员 to、from、cc、bcc、subject、body,并且可能包含这些方法 saveAsDraft()、send()、discardDraft()。但是 login() 不应该在这里,因为有许多电子邮件协议,应该单独实现。
凝聚力通常使用 LCOM(缺乏凝聚力)指标之一来衡量,原始 LCOM 指标来自 Chidamber 和 Kemerer。参见例如: http: //www.computing.dcu.ie/~renaat/ca421/LCOM.html
一个更具体的例子:如果一个类有一个私有字段和三个方法;当所有三个方法都使用该字段执行操作时,该类具有很强的凝聚力。
内聚类的伪代码:
class FooBar {
private SomeObject _bla = new SomeObject();
public void FirstMethod() {
_bla.FirstCall();
}
public void SecondMethod() {
_bla.SecondCall();
}
public void ThirdMethod() {
_bla.ThirdCall();
}
}
例如,如果一个类具有三个私有字段和三个方法;当所有三种方法只使用三个字段之一时,则该类的凝聚力很差。
内聚性差的类的伪代码:
class FooBar {
private SomeObject _bla = new SomeObject();
private SomeObject _foo = new SomeObject();
private SomeObject _bar = new SomeObject();
public void FirstMethod() {
_bla.Call();
}
public void SecondMethod() {
_foo.Call();
}
public void ThirdMethod() {
_bar.Call();
}
}
类做一件事原则是来自 Robert C. Martin的单一职责原则,是SOLID原则之一。该原则规定一个类应该只有一个改变的理由。
保持单一职责原则可能会导致代码更具凝聚力,但在我看来,这是两件不同的事情。
这是一个低内聚的例子:
class Calculator
{
public static void main(String args[])
{
//calculating sum here
result = a + b;
//calculating difference here
result = a - b;
//same for multiplication and division
}
}
但是高内聚意味着类中的函数做它们应该做的事情(就像它们被命名一样)。而不是某些功能可以完成其他功能的工作。因此,以下可能是高内聚的示例:
class Calculator
{
public static void main(String args[])
{
Calculator myObj = new Calculator();
System.out.println(myObj.SumOfTwoNumbers(5,7));
}
public int SumOfTwoNumbers(int a, int b)
{
return (a+b);
}
//similarly for other operations
}
考虑内聚原则的一般方法是,您应该将一个代码与其他依赖于它或依赖于它的代码一起定位。凝聚力可以而且应该应用于高于班级水平的构成水平。例如,理想情况下,包或命名空间应包含与某些常见主题相关的类,并且与其他包/命名空间的依赖相比,它们之间的相互依赖程度更高。即保持本地依赖。
凝聚力一词最初用于描述源代码模块,作为模块源代码相互关联程度的定性度量。凝聚力的概念被用于各种领域。例如,一群人(例如军事单位)可能具有凝聚力,这意味着该单位中的人们为了共同的目标而共同努力。
源代码内聚的本质是模块中的源代码朝着一个共同的、明确定义的目标一起工作。创建模块输出所需的最少源代码在模块中,仅此而已。接口定义明确,输入通过接口流入,输出通过接口流出。没有副作用,强调极简主义。
功能内聚模块的一个好处是开发和自动化单元测试很简单。事实上,一个模块凝聚力的一个很好的衡量标准是为模块创建一整套详尽的单元测试是多么容易。
模块可以是面向对象语言的类,也可以是函数式语言或非面向对象语言(如 C)中的函数。这一测量内聚力领域的许多原始工作主要涉及早在 IBM 的 COBOL 程序。 1970 年代所以凝聚力绝对不仅仅是一个面向对象的概念。
产生内聚概念和相关联的耦合概念的研究初衷是研究程序易于理解、维护和扩展的特点在哪里。目标是能够学习编程的最佳实践,编纂这些最佳实践,然后将这些实践传授给其他程序员。
优秀程序员的目标是在给定环境和要解决的问题的情况下编写内聚性尽可能高的源代码。这意味着在大型应用程序中,源代码主体的某些部分与其他部分的源代码在该模块或类中的内聚程度不同。有时,由于您要解决的问题,您可以获得的最佳效果是时间或顺序凝聚力。
最好的凝聚力是功能凝聚力。具有功能凝聚力的模块类似于数学函数,因为您提供一组输入并获得特定的输出。一个真正的功能模块除了输出之外不会有副作用,也不会保持任何状态。相反,它将具有一个定义明确的接口,该接口封装了模块的功能,而不会暴露模块的任何内部结构,并且使用该模块的人将提供一组特定的输入并获得特定的输出作为回报。一个真正的功能模块也应该是线程安全的。
许多编程语言库包含许多功能模块的示例,无论是类、模板还是函数。最具功能性的内聚示例是数学函数,例如正弦、余弦、平方根等。
其他功能可能会产生副作用或维持某种状态,从而导致这些功能的使用更加复杂。
例如,抛出异常或设置全局错误变量(errno
在 C 中)或必须按顺序使用(strtok()
函数是标准 C 库中的一个示例,因为它维护内部状态)或提供指针的函数,然后必须被管理或向某些日志实用程序发出日志都是不再具有功能凝聚力的功能的示例。
我读过 Yourdon 和 Constantine 的原著《结构化编程》,在 1980 年代我第一次接触到内聚的概念,以及 Meilir Page-Jones 的《结构化系统设计实用指南》一书,Page-Jones 在描述方面做得更好耦合和内聚。Yourdon 和 Constantine 的书似乎更具学术性。Steve McConnell 的书 Code Complete 非常好且实用,修订版有很多关于良好编程实践的内容。
大多数答案都没有解释什么是内聚力,它在鲍勃叔叔的书清洁代码中得到了很好的定义。
类应该有少量的实例变量。一个类的每个方法都应该操作一个或多个这些变量。一般来说,一个方法操作的变量越多,该方法对其类的凝聚力就越大。每个方法都使用每个变量的类具有最大的内聚性。一般来说,创建这种最大凝聚力的类既不可取,也不可能;另一方面,我们希望凝聚力高。当内聚度高时,意味着类的方法和变量是相互依赖的,并且作为一个逻辑整体挂在一起。
让我用一个类定义来解释它
class FooBar {
private _bla;
private _foo;
private _bar;
function doStuff()
if(this._bla>10){
this._foo = 10;
this._bar = 20;
}
}
function doOtherStuff(){
if(this._foo==10){
this._bar = 100;
this._bla = 200;
}
}
}
如果您看到上面的示例,则类是内聚的,这意味着变量在类之间共享以一起工作,更多的变量被共享,这意味着类是高度内聚的并且作为一个单元工作。
内聚意味着一个类或方法只完成一项定义的工作。方法或类的名称也应该是不言自明的。例如,如果您编写一个计算器,您应该将类命名为“calculator”而不是“asdfghj”。您还应该考虑为每个任务创建一个方法,例如subtract() add() 等...将来可能使用您的程序的程序员确切地知道您的方法在做什么。好的命名可以减少评论工作
还有一个原则是 DRY - 不要重复自己
在这种情况下, MSDN关于它的文章可能比 Wikipedia 提供更多信息。