【问题标题】:Coupling, Cohesion and the Law of Demeter耦合、内聚和得墨忒耳定律
【发布时间】:2008-10-02 15:40:11
【问题描述】:

Law of Demeter 表示您应该只与您直接了解的对象交谈。也就是说,不要执行方法链接来与其他对象对话。当你这样做时,你正在与中间对象建立不正确的链接,不恰当地coupling你的代码与其他代码。

这很糟糕。

解决方案是让您知道的类本质上公开简单的包装器,将责任委托给与其有关系的对象。

这很好。

但是,这似乎导致班级的cohesion 较低。它不再只是简单地负责它所做的事情,而且它还具有某种意义上的委托,通过复制其相关对象的接口部分来降低代码的内聚性。

这很糟糕。

这真的会降低凝聚力吗?两害相权取其轻?

这是发展的灰色地带之一,您可以在其中讨论界限在哪里,或者是否有强有力的、有原则的方法来决定在哪里划定界限以及您可以使用什么标准来做出决定?

【问题讨论】:

  • 虽然有点离题,但我强烈推荐 Ted Faisons “基于事件的编程:将事件发挥到极致”link。它对常见的耦合问题进行了很好的分解,并提供了一个很好的系统来衡量给定系统中的耦合量
  • 违反 LoD 时,附加链接是否“不正确”取决于链接类的性质:如果这些非常突出(想想 Row、Column、Cell in Excel 的实现),与它们的耦合是没有问题的,而且 LoD 的限制过于严格。同样,如果有问题的类是有目的的纯粹的数据类,基本上没有行为。

标签: oop refactoring coupling law-of-demeter cohesion


【解决方案1】:

Grady Booch 在“面向对象的分析与设计”中:

“凝聚力的思想也来源于结构化设计。简单来说,凝聚力 测量单个模块的元素之间的连接程度(和 对于面向对象的设计,单个类或对象)。最不受欢迎的形式 内聚是巧合的内聚,其中完全不相关的抽象是 扔到同一个类或模块中。例如,考虑一个类,包括 狗和航天器的抽象,它们的行为完全不相关。这 最理想的内聚形式是功能内聚,其中的要素 一个类或模块一起工作以提供一些有界的行为。 因此,如果 Dog 类的语义包含行为,那么它在功能上是内聚的 一条狗,整条狗,除了狗什么都没有。”

将上面的 Dog 替换为 Customer 可能会更清楚一些。因此,目标实际上只是针对功能凝聚力并尽可能远离巧合凝聚力。根据您的抽象,这可能很简单,也可能需要进行一些重构。

注意内聚作用于一个“模块”而不是单个类,即一组一起工作的类。因此,在这种情况下,Customer 和 Order 类仍然具有良好的内聚性,因为它们具有很强的关系,客户创建订单,订单属于客户。

Martin Fowler 说他更愿意称其为“Demeter 的建议”(参见文章 Mocks aren't stubs):

“Mockist 测试人员确实更多地谈论避免'火车残骸' - getThis().getThat().getTheOther() 风格的方法链。避免方法链也被称为遵循得墨忒耳法则。虽然方法链是一种气味,中间人对象被转发方法臃肿的相反问题也是一种气味。(我一直觉得如果它被称为Demeter的建议我会更喜欢Demeter法则 .)"

这很好地总结了我的出发点:与严格遵守“法律”可能要求的相比,具有更低水平的凝聚力是完全可以接受的,而且通常需要更低的凝聚力。避免巧合的凝聚力并以功能凝聚力为目标,但不要拘泥于在需要更自然地适应您的设计抽象的地方进行调整。

【讨论】:

  • 我喜欢这个回复,它似乎暗示它是一个灰色的东西。那么,如果我可以解释一下,您是否会争辩说,只要您在所涉及的对象中保持高度凝聚力,违反得墨忒耳法则是可以接受的?
  • 当然,这是一个很好的表达方式。顺便说一句,你见过 JQuery,方法调用 3 级以上的链接很常见。尽管可能是在不同的背景下(更程序化),但来自 JQuery 的人说 Java/C#/C++ 将有各种“忘却”的坏习惯!
  • 如果我可以建议您将这一点添加到您的帖子中(关于违反得墨忒耳法则是可以的)...现在这是我见过的最好的回应。
  • 我刚刚遇到的另一个来源。在下一页上搜索 Demeter,并将其包含在您的回复中...martinfowler.com/articles/mocksArentStubs.html
  • 布拉德利,完成了,顺便说一句好话。希望这对其他人也有用。
【解决方案2】:

如果你违反了得墨忒耳法则

int price = customer.getOrder().getPrice();

解决方法是不要创建getOrderPrice()并将代码转换成

int price = customer.getOrderPrice();

但要注意这是code smell,并进行相关更改,希望既能增加内聚力,又能降低耦合度。不幸的是,这里没有始终适用的简单重构,但您可能应该申请 tell don't ask

【讨论】:

  • “告诉,不要问”的链接非常值得一读。谢谢!
  • “告诉,不要问”也让您更接近问题域(用户需求)中的抽象,而不是实现细节(设计或派生需求)。
【解决方案3】:

我认为您可能误解了凝聚力的含义。一个类按照其他几个类来实现,内聚度不一定低,只要代表一个明确的概念,有明确的目的。例如,您可能有一个class Person,它是根据课程Date(出生日期)、AddressEducation(此人上过的学校列表)来实现的。您可以在Person 中提供包装以获取出生年份、上一所学校或他居住的州,以避免暴露Person 是根据其他类实现的事实。这会减少耦合,但会使Person 的凝聚力不会降低。

【讨论】:

    【解决方案4】:

    这是一个灰色地带。 这些原则旨在帮助您完成工作,如果您发现自己正在为他们工作(即它们妨碍您和/或您发现它使您的代码过于复杂),那么您太难遵守了,您需要后退。

    让它为你工作,不要为它工作。

    【讨论】:

    • 这是对得墨忒耳法则的一个很好的描述“让它为你工作,不要为它工作。”
    【解决方案5】:

    我不知道这是否真的会降低凝聚力。

    聚合/组合都是关于一个类利用其他类来满足它通过其公共方法公开的契约。 该类不需要复制其相关对象的接口。它实际上向方法调用者隐藏了有关这些聚合类的任何知识。

    在多级类依赖的情况下要遵守得墨忒耳定律,你只需要在每一级应用聚合/组合和良好的封装。

    换句话说,每个类对其他类都有一个或多个依赖项,但是这些只是对被引用类的依赖,而不是对从属性/方法返回的任何对象的依赖。

    【讨论】:

    • 但是,以另一个海报示例为例,如果客户对象现在不仅需要了解客户的订单,还需要了解客户订单的价格,那么这就是将客户的界面弄乱了这似乎会降低凝聚力。
    • 这也与所使用的抽象级别/类型有关。根据定义,客户必须与订单有某种密切的关联。因此,customer.getOrderPrice() 不会降低客户的凝聚力;重构。例如:客户可以使用简单的小数作为 orderPrice。
    • 如何不降低凝聚力?它与客户无关,它与订单有关。您只是在客户级别公开它。它什么时候开始降低凝聚力? customer.getOrderAddressStreet() 会导致凝聚力下降吗?也许这一切都是灰色的......
    • 在典型情况下,订单由客户创建。没有客户的订单无效。如果某事与订单相关,则它也与创建该订单的客户相关。客户和订单一起工作以提供一些有界的行为。请参阅我的附加答案。
    【解决方案6】:

    在耦合和内聚之间似乎存在权衡的情况下,我可能会问自己“如果其他人已经编写了这个逻辑,并且我正在寻找其中的错误,我会首先查看哪里? ",然后这样写代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多