耦合,内聚和Demeter定律
得墨meter耳定律表明,我们只能与直接了解的物体说话。即,不要执行方法链接来与其他对象交谈。这样做时,我们正在与中间对象建立不正确的链接,从而不恰当地将代码耦合到其他代码。
那很糟。
解决方案将是针对我们确实知道的类,从本质上公开简单的包装器,这些包装器将职责委托给与之有关系的对象。
那挺好的。
但是,这似乎导致班级的凝聚力低。它不再只是对它的确切功能负责,而是在某种意义上也具有委托,这可以通过复制相关对象接口的某些部分来降低代码的内聚性。
那很糟。
真的会降低凝聚力吗?是两个邪恶中的较小者吗?
这是发展的灰色领域之一,在这里我们可以讨论分界线在哪里,或者是否有强有力的原则性方法来决定分界线以及可以使用哪些标准来做出决定?
解决方案
在耦合和内聚之间似乎需要权衡的情况下,我可能会问自己"其他人是否已经编写了此逻辑,而我正在寻找其中的错误,我应该首先去哪里?"这样写代码。
如果我们违反了《得墨meter耳法则》
int price = customer.getOrder().getPrice();
解决方案是不创建getOrderPrice()并将代码转换为
int price = customer.getOrderPrice();
但要注意,这是一种代码味道,并进行了相应的更改,希望既增加内聚力又降低耦合度。不幸的是,这里没有总是适用的简单重构,但是我们可能应该应用告诉不要问
我不知道这是否会降低凝聚力。
聚合/组合都是关于一个类的,它利用其他类来满足它通过其公共方法公开的契约。
该类不需要复制其相关对象的接口。它实际上是在方法调用者中隐藏有关这些聚合类的任何知识。
为了在多个级别的类依赖情况下遵守Demeter的定律,我们只需要在每个级别应用聚合/组合和良好的封装即可。
换句话说,每个类都对其他类具有一个或者多个依赖关系,但是,它们仅是对所引用类的依赖,而与从属性/方法返回的任何对象无关。
我认为我们可能误解了凝聚力的含义。根据其他几个类别实现的类别不一定具有较低的内聚性,只要它表示明确的概念并且具有明确的目的即可。例如,我们可能有一个" Person"类,该类根据" Date"(日期)(出生日期)," Address"和" Education"(该人去过的学校的列表)的类来实现。我们可以在"人员"中提供包装器,以获取出生年份,该人上的最后一所学校或者他所居住的州,以避免暴露"人员"在其他类别中得到实施的事实。这样可以减少耦合,但是可以使" Person"的内聚性降低。
它是一个灰色区域。
这些原则旨在为工作提供帮助,如果我们发现自己在为他们工作(例如,它们妨碍了工作,和/或者我们发现使代码复杂化),那么我们就很难遵循,因此需要退后一步。
让它为我们工作,不要为它工作。
Grady Booch在"面向对象的分析和设计"中:
"凝聚力的想法也来自结构化设计。简单地说,凝聚力
测量单个模块的各个元素之间的连通程度(以及
对于面向对象的设计,可以使用单个类或者对象)。最不希望的形式
凝聚力是巧合的凝聚力,其中完全不相关的抽象是
扔到相同的类或者模块中。例如,考虑一个包含以下内容的类
狗和航天器的抽象,它们的行为是完全无关的。这
凝聚力最理想的形式是功能凝聚力,其中
一个类或者模块一起工作以提供一些良好的行为。
因此,如果Dog类的语义包含行为,则该类在功能上具有凝聚力
一只狗,整个狗,只剩下那只狗。"
在上面用"客户"替换"狗",它可能会更清晰一些。因此,目标的真正目的只是为了实现功能上的凝聚力,并尽可能地摆脱巧合的凝聚力。根据抽象,这可能很简单,也可能需要一些重构。
请注意,内聚对"模块"的作用与对单个类(即一组协同工作的类)的作用相同。因此,在这种情况下,Customer和Order类仍然具有良好的凝聚力,因为它们具有强大的关系,客户创建订单,订单属于客户。
马丁·福勒(Martin Fowler)表示,将其称为" Demeter的建议"会更自在(请参阅文章"嘲笑不是存根"):
"模拟测试人员确实会更多地讨论避免使用getThis()。getThat()。getTheOther()样式的"火车残骸"方法链。避免方法链也被称为遵循Demeter定律。中间人用转发方法肿的对象的相反问题也是一种气味。(我一直觉得如果被称为Demeter的建议,我会对Demeter法则更加自在。)"
这很好地总结了我的来历:完全严格地接受"法律"可能需要的凝聚力是完全可以接受的,并且经常是必要的。避免巧合的内聚力,并寻求功能上的内聚力,但不要挂在需要更自然地与设计抽象相适应的地方。