Law of Demeter表示您应该只与您直接了解的对象交谈。也就是说,不要执行方法链接与其他对象交谈。当您这样做时,您正在与中间对象建立不正确的链接,不恰当coupling您的代码与其他代码。
那很糟糕。
解决方案是针对您所知道的类,基本上公开简单的包装器,将责任委托给它与之关系的对象。
那很好。
但是,这似乎导致班级cohesion低。它不再只是简单地对其所做的事情负责,但它也有代表在某种意义上,通过复制其相关对象的部分界面使代码更具凝聚力。
那很糟糕。
它真的会降低凝聚力吗?它是两个邪恶中的较小者吗?
这是发展的灰色区域之一,您可以在哪里讨论该线的位置,或者是否有强有力的原则性方法来决定在哪里划线以及您可以使用哪些标准来做出决定?
答案 0 :(得分:45)
Grady Booch的“面向对象分析与设计”:
“凝聚力的概念也来自结构化设计。简单地说,凝聚力 衡量单个模块的元素之间的连接程度(和 用于面向对象的设计,单个类或对象)。最不可取的形式 凝聚力是巧合的凝聚力,其中完全不相关的抽象是 扔进同一个类或模块。例如,考虑一个包含的类 狗和航天器的抽象,其行为是非常不相关的。该 最理想的凝聚力形式是功能凝聚力,其中的元素是 一个类或模块都协同工作以提供一些有条理的行为。 因此,如果它的语义包含行为,那么类Dog在功能上是有凝聚力的 一只狗,整条狗,只有狗。“
在上面与顾客合作,可能会更清楚一点。因此,目标实际上只是为了实现功能凝聚力,并尽可能地摆脱巧合的凝聚力。根据您的抽象,这可能很简单,也可能需要进行一些重构。
注意凝聚力同样适用于“模块”而不是单个类,即一组一起工作的类。所以在这种情况下,客户和订单类仍然具有良好的凝聚力,因为他们有这种强大的关系,客户创建订单,订单属于客户。
Martin Fowler说他更习惯称之为“Demeter的建议”(参见文章Mocks aren't stubs):
“Mockist测试人员确实更多地谈论避免'火车残骸' - getThis()风格的方法链.getThat()。getTheOther()。避免方法链也被称为遵循Demeter法则。虽然方法链是一个气味,中间人对象臃肿与转发方法相反的问题也是一种气味。(我一直觉得如果它被称为得墨忒耳的建议,我会更熟悉得墨忒耳的法则。)“
这总结了我来自哪里:完全可以接受并且通常必须具有较低的凝聚力,而不是严格遵守“法律”所要求的。避免巧合的凝聚力,并注重功能凝聚力,但不要因为需要更加自然地适应你的设计抽象而不知所措。
答案 1 :(得分:20)
如果您违反了得墨忒耳法则
int price = customer.getOrder().getPrice();
解决方案不是创建getOrderPrice()并将代码转换为
int price = customer.getOrderPrice();
但请注意,这是code smell并进行相关更改,希望这些更改都会增加内聚力和降低耦合度。不幸的是,这里没有简单的重构总是适用,但你应该应用tell don't ask
答案 2 :(得分:6)
我认为你可能误解了凝聚力的含义。根据其他几个类实现的类不一定具有低内聚性,只要它代表一个清晰的概念,并且具有明确的目的。例如,您可能有一个class Person
,它是根据类Date
(出生日期),Address
和Education
(学校列表)实施的人去了)。您可以在Person
中提供包装,以获取出生年份,该人去过的最后一所学校或他所居住的州,以避免暴露Person
以其他方式实施的事实类。这会减少耦合,但它会使Person
具有同样的凝聚力。
答案 3 :(得分:4)
这是一个灰色区域。 这些原则是为了帮助你的工作,如果你发现你正在为他们工作(即他们妨碍你和/或你发现它使你的代码复杂化)那么你就太过努力了,你需要退后。
让它为你工作,不要为它工作。
答案 4 :(得分:1)
我不知道这是否会降低凝聚力。
聚合/组合都是关于利用其他类来满足其通过其公共方法公开的合同的类。 该类不需要复制它的相关对象的接口。它实际上隐藏了来自方法调用者的关于这些聚合类的任何知识。
在多级别依赖的情况下,要遵守Demeter的规律,你只需要在每个级别应用聚合/组合和良好的封装。
换句话说,每个类对其他类有一个或多个依赖关系,但是这些只依赖于引用的类,而不依赖于从本地/方法返回的任何对象。
答案 5 :(得分:0)
在耦合和凝聚之间似乎存在权衡的情况下,我可能会问自己“如果其他人已经写过这个逻辑,而我正在寻找其中的错误,我会先在哪里看? “,并以这种方式编写代码。