内聚性和耦合度之间的区别

670

内聚性和耦合度有何区别?

耦合度和内聚性如何影响软件设计的好坏?

可以举一些例子来说明它们之间的区别以及对代码质量的整体影响。


3
请查看此链接:http://msdn.microsoft.com/zh-cn/magazine/cc947917.aspx。 - Inv3r53
6
我想指出这篇文章:逐步实现 S.O.L.I.D. 软件开发。感谢,Kris. - Kris van der Mast
5
这是关于"内聚性"和"耦合性"差异的最新文章。 - janisz
4
好的,下面是翻译的内容:请参见:https://dev59.com/XXVD5IYBdhLWcg3wQJOT - davidpricedev
1
实际上,这是那个的副本。 - cellepo
在过去的几年里,我经常回到这个页面,并经常重新发现这些答案是多么主观。在我看来,这是因为这些词被主观地使用了。当我们喜欢两件事情的组合时,我们说“良好的内聚性”,而当它们不好时,我们说“紧密耦合”。去掉“好/紧密”会让人感到困惑 - 但简而言之:某物越不同,它应该离得越远。显然,这对于团队来说效果不太好 - 这与多样性相反。 - Ben Butterworth
16个回答

943

内聚性(Cohesion)指的是一个类(或模块)可以做什么。低内聚性意味着该类执行多种操作,即它是广泛的,不专注于应该做什么。高内聚性意味着该类专注于应该做什么,即只包含与类意图相关的方法。

低内聚性的示例:

-------------------
| Staff           |
-------------------
| checkEmail()    |
| sendEmail()     |
| emailValidate() |
| PrintLetter()   |
-------------------

高内聚的例子:

----------------------------
| Staff                   |
----------------------------
| -salary                 |
| -emailAddr              |
----------------------------
| setSalary(newSalary)    |
| getSalary()             |
| setEmailAddr(newEmail)  |
| getEmailAddr()          |
----------------------------

耦合而言,它指的是两个类/模块之间相关或依赖性的程度。低耦合的类别,在一个类中做出重大改变不应该影响另一个类。高耦合会使更改和维护代码变得困难;因为类之间紧密相连,所以进行更改可能需要整个系统重新设计。

良好的软件设计具有高内聚性低耦合度


23
请问如何通过删除一些方法并添加其他方法来提高内聚性,我不太明白。可以有人帮忙解释一下吗? - Saket Jain
15
@SaketJain,这不仅仅是删除一些方法并添加一些其他方法。而是要关注这些方法与类的目的之间的关系(如果这个解释更清晰的话)。 - mauris
6
顶部的低内聚性示例看起来很不错,我认为您意外地想说“高内聚性”。 - relipse
66
Staff类不是我们检查、发送或验证电子邮件的地方。这些功能应该放在一个假设的Email类中,这就是为什么它的内聚性较低的原因。在第二个示例中,Staff类仅包含设置和获取与员工相关的数据的适当信息。它们不执行应由另一个类管理的操作。 - Antonio Pantano
4
@MehrajMalik,他们易于理解。但我认为“单一职责原则”是“高内聚低耦合”的原因之一(结果)。深入思考每个职责,你可以在系统中获得独立的组件,并且它们之间的通信更好。 - imtk
显示剩余5条评论

99

模块内高内聚,模块间低耦合常被认为与面向对象编程语言的高质量相关。

例如,每个Java类中的代码必须具有高内部内聚性,但与其他Java类中的代码松散耦合。

Meyer的面向对象软件构造(第二版)的第三章对这些问题进行了很好的描述。


5
这些概念并不仅限于面向对象编程。如果有的话,我建议面向对象语言的目标是引导程序员朝向高内聚/低耦合的目标。 - Hutch
Bertrand Meyer非常慷慨地在他的网站上免费发布了他的书籍(链接已更新)。 - Christophe

94

内聚性(Cohesion)是软件元素的责任相关和重心集中程度的指示。

耦合度(Coupling)则指软件元素与其他元素连接的强度。

这些软件元素可以是类、包、组件、子系统或系统。在设计系统时,建议选择具有高内聚和支持低耦合的软件元素。

低内聚会导致大而松散的类,难以维护、理解和减少可重用性。同样,高耦合会导致紧密耦合的类,改动可能不局限于本地,难以更改并减少重用。

我们可以使用一个虚构的场景来说明,我们正在设计一个可监视的ConnectionPool,其需求如下所示。请注意,对于像ConnectionPool这样的简单类来说,这似乎太多了,但基本意图只是通过一些简单的例子演示低耦合高内聚,我认为这应该有所帮助。

  1. 支持获取连接
  2. 释放连接
  3. 获取有关连接与使用计数的统计信息
  4. 获取连接与时间的统计信息
  5. 将连接检索和释放信息存储到数据库中以供以后报告。

使用低内聚性,我们可以通过将所有这些功能/责任强制塞入单个类中来设计ConnectionPool类,如下所示。我们可以看到,这个单一类负责连接管理、与数据库交互以及维护连接统计信息。

Low Cohesion Connection Pool

使用高内聚性,我们可以将这些责任分配到不同的类中,使其更易于维护和重复使用。

高内聚连接池

为了展示低耦合,我们将继续使用上面的高内聚ConnectionPool图。如果我们看一下上面的图表,虽然它支持高内聚,但是ConnectionPoolConnectionStatistics类和PersistentStore紧密耦合,直接与它们交互。为了减少耦合,我们可以引入一个ConnectionListener接口,并让这两个类实现该接口,并让它们在ConnectionPool类中注册。然后ConnectionPool将遍历这些监听器并通知它们连接获取和释放事件,从而实现更少的耦合。

低耦合连接池

注意/警告:对于这种简单的情况,看起来可能有点过度设计,但是如果我们想象一个实时场景,其中我们的应用程序需要与多个第三方服务交互以完成事务:直接将我们的代码与第三方服务耦合在一起意味着任何第三方服务的更改都可能导致我们的代码在多个地方发生更改,而不是这样,我们可以有一个Facade,它在内部与这些多个服务进行交互,并且对服务的任何更改都将局限于Facade中,并强制实现与第三方服务的低耦合。


4
非常好的回答!如果可能的话,能否使用其他示例来解释?连接池对于每个人来说可能不是很清楚。无论如何,这确实对我有所帮助。所以谢谢! - Saket Jain
使用ConnectionListener接口如何帮助减少耦合?您能提供一个更容易理解的例子吗? - abhishek gupta
2
@abhishekgupta 在这个例子中,您可能已经注意到我们使用了观察者模式来实现低耦合。阅读此内容将有助于理解观察者如何创建松散耦合的设计? - Madhusudana Reddy Sunnapu

60

简单来说,内聚度表示代码库中的某个部分是否形成逻辑上的单一、原子单位。而耦合度则表示一个单元对其他单元的依赖程度。换句话说,它是两个或多个单元之间连接的数量。连接数越少,耦合度就越低。

本质上,高内聚意味着将相互关联的代码部分放在一个地方。同时,低耦合是指尽可能地将不相关的代码部分分开。

从内聚度和耦合度的角度来看,代码有以下类型:

理想的代码遵循这些准则。它松散耦合且高度内聚。我们可以用这张图片来说明这种代码:enter image description here

万能对象是高内聚和高耦合的结果。它是反模式,基本上是指一段代码同时完成所有工作: enter image description here

选择不当是当不同类或模块之间的边界被选择不当时发生的现象。此处输入图片描述

破坏性解耦是最有趣的一种情况。它有时会在程序员试图过度解耦代码库以至于代码完全失去焦点时发生:此处输入图片描述

这里了解更多信息。


1
优秀的文章和插图!如果我可以提出一个改进建议,我喜欢“选择不当”将语义无关的组件组织在小群中,但我认为它们之间应该有更多的箭头可见。毕竟,即使在您的4个方块图表中,这也是落入“耦合”轴上限范围的一个。 - Slawomir Brzezinski
1
我还想说,“选择不当”的每个群集内应该有更少的箭头。使用您文章中的“文件夹结构”示例,您将其归类为“选择不当”的存储库或工厂肯定不会相互通信。 - Slawomir Brzezinski
更新:我已向该图像的原作者提出了这些建议,作者同意了 - Slawomir Brzezinski
1
那是一个很好的答案,解释得非常清楚。我发现最常见的“选择不当”的形式之一是所谓的“类型凝聚”。你可以在各个地方看到这种情况,例如一个包结构包含“屏幕”,所有的屏幕都在其中,另一个包叫做“对话框”,所有的对话框都在那个包中。结果是你会在一个地方得到MyABDialog,在另一个地方得到MyABScreen。 这样组织实际上破坏了内聚性,并引入了不必要的耦合,同时看起来像是应用了良好的模式。 - Brill Pappin

34
Cohesion(内聚性)最好的解释来自于 Uncle Bob 的《Clean Code》:
每个类应该只有少量实例变量。类的每个方法应该操作其中一个或多个变量。一般来说,方法操作的变量越多,该方法对其类的内聚性就越高。在一个类中,每个变量都被每个方法使用时,它的内聚性是最大的。
一般情况下,不建议也不可能创建这样的最大内聚性类;另一方面,我们希望内聚性很高。当内聚性很高时,意味着类的方法和变量是相互依赖的,并作为一个逻辑整体存在。
保持函数小并保持参数列表短的策略有时会导致实例变量的增多,这些实例变量被子集方法使用。当发生这种情况时,几乎总是意味着至少还有一个其他类试图从更大的类中分离出来。您应该尝试将变量和方法分离成两个或更多类,以使新的类更具内聚性。

2
我同意这可能是最好的解释,这就是我喜欢 Uncle Bob 的原因,他可以用几个短语解释实际含义。了解了这个定义,你可以立即看出应该对给定的类做什么来增加它的内聚性。 - Pawel Dubiel
这是在面向对象设计特定背景下的很好的解释,但这些概念更普遍适用。其他回答和(博客参考)在更一般的背景下提供了很好的解释。 - Eric Zoerner

34

增强内聚性和减少耦合确实会导致良好的软件设计。

内聚性将您的功能划分为紧凑的部分,并使其最接近与其相关的数据,而解耦确保功能实现与系统的其余部分隔离。

解耦允许您更改实现而不影响软件的其他部分。

内聚性确保实现更加专注于功能,并且同时更易于维护。

减少耦合并增加内聚性的最有效方法是通过接口进行设计

也就是说,主要的功能对象只能通过它们实现的接口之间'了解'彼此。

接口的实现自然地引入了内聚性。

虽然在某些场景中不太现实,但应该把这作为设计目标来工作。

示例(非常简略):

public interface IStackoverFlowQuestion
      void SetAnswered(IUserProfile user);
      void VoteUp(IUserProfile user);
      void VoteDown(IUserProfile user);
}

public class NormalQuestion implements IStackoverflowQuestion {
      protected Integer vote_ = new Integer(0);
      protected IUserProfile user_ = null;
      protected IUserProfile answered_ = null;

      public void VoteUp(IUserProfile user) {
           vote_++;
           // code to ... add to user profile
      }

      public void VoteDown(IUserProfile user) {
          decrement and update profile
      }

      public SetAnswered(IUserProfile answer) {
           answered_ = answer
           // update u
      }
}

public class CommunityWikiQuestion implements IStackoverflowQuestion {
     public void VoteUp(IUserProfile user) { // do not update profile }
     public void VoteDown(IUserProfile user) { // do not update profile }
     public void SetAnswered(IUserProfile user) { // do not update profile }
}

在你的代码库中的其他地方,可能会有一个模块来处理问题,不论这些问题是什么:

public class OtherModuleProcessor {
    public void Process(List<IStackoverflowQuestion> questions) {
       ... process each question.
    }
}

16

内聚性在软件工程中是指一个模块的元素彼此之间关联程度的度量。因此,它是通过软件模块源代码表达的每个功能块之间相关性的度量。

耦合性简而言之,是一个组件(再想象一下一个类,虽然不一定)对另一个组件的内部工作或内部元素了解程度的度量,即它对另一个组件的了解程度。

我在博客中写了一篇文章,如果您想详细了解一些例子和图示,请阅读该文章。我认为它能回答你大部分的问题。


13

在此输入图片描述

内聚是指单个类的设计如何。内聚是面向对象的原则中与确保一个类具有单一、明确目的最密切相关的原则。

一个类的内聚性越强,它就越专注于特定目标。高内聚度的优点是这样的类比低内聚度的类更容易维护(并且不需要频繁地更改)。另一个好处是,具有明确目的的类往往比其他类更容易重用。

在上图中,我们可以看到,在低内聚度中,只有一个类负责执行许多不常见的任务,这降低了重用和维护的机会。但在高内聚度中,每个任务都有一个独立的类来执行特定的任务,从而实现更好的可用性和维护性。


3
内聚性(Cohesion):Co表示“一起”,hesion表示“粘合”。不同物质颗粒之间粘合在一起的系统。 现实生活中的例子: enter image description here
图片来源

整体大于部分之和——亚里士多德。

  • 内聚性是一种排序类型的度量,通常描述为“高内聚”或“低内聚”。具有高内聚性的模块往往更可取,因为高内聚性与软件的多个优良特性相关,包括强健性、可靠性、可重用性和易理解性。相反,低内聚性则与难以维护、测试、重用甚至理解等不良特性相关。维基百科

  • 耦合度通常与内聚性相对比。低耦合度通常与高内聚性相关,反之亦然。低耦合度通常是良好结构化计算机系统和良好设计的标志,当与高内聚性相结合时,支持高可读性和可维护性的一般目标。维基百科


2
内聚性是模块相对功能强度的指示。
  • 一个高内聚的模块执行单一任务,与程序其他部分的组件交互很少。简而言之,高内聚的模块应该(理想情况下)只做一件事。
  • 传统观点:

    模块的“专一性”

  • 面向对象的观点:

    内聚意味着组件或类仅封装与其自身及其彼此紧密相关的属性和操作。

  • 内聚性级别

     功能性

     层次结构

     通信

     顺序

     过程

     时间

     实用

耦合性是模块之间相互依赖关系的指示。
  • 耦合性取决于模块之间的接口复杂性、对模块进行入口或引用的位置以及跨接口传输的数据。

  • 传统观点:

    组件与其他组件及外部世界之间的连接程度

  • 面向对象的观点:

    类相互连接的定性度量

  • 耦合性级别

     内容耦合

     公共耦合

     控制耦合

     标记耦合

     数据耦合

     例程调用

     类型使用

     包含或导入

     外部 #


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接