具有数据模型对象的Demeter定律

时间:2014-09-24 15:43:12

标签: java design-patterns law-of-demeter

我昨天从假期回来工作,在我们的日常站立中,我的队友们提到他们正在重构我们的java代码中的所有模型对象,以删除所有的getter和setter,并使模型字段成为所有公共对象,调用德米特定律作为这样做的原因是因为

  

促进我们遵守得墨忒耳定律:模块不应该知道它操纵的“对象”的内部。自数据   结构不包含任何行为,它们自然地暴露了它们的内部结构。因此,在这种情况下,德米特不适用。

我承认我必须了解我对LoD的了解,但对于我的生活,我找不到任何迹象表明这符合法律的精神。我们模型中的getter / setter都不包含任何业务逻辑,这是他这样做的理由,因此这些对象的客户端无需了解是否在get / set方法中执行了某些业务逻辑。 / p>

我认为这是对需要'对象结构的内部知识'意味着什么的误解,或者至少在字面意义上并且在这个过程中打破了一个非常标准的约定。

所以我的问题是,直接暴露模型对象内部结构而不是通过LoD名称中的getter / setter实际上是否有意义?

3 个答案:

答案 0 :(得分:16)

罗伯特·马丁有一本名为“清洁代码”的书,涵盖了这一点。

在第6章(对象和数据结构)中,他谈到了对象和数据结构之间的根本区别。 对象受益于封装,而数据结构却没有。

有一节关于得墨忒耳的法则:

  

有一个着名的启发式方法Law of Demeter,它说模块不应该知道它操纵的对象的内部。正如我们在上一节中看到的,对象隐藏了它们的数据并公开了操作。这意味着对象不应该通过访问器暴露其内部结构,因为这样做是为了暴露而不是隐藏其内部结构。

     

更准确地说,得墨忒耳法则说C类方法只应该调用这些方法:

     
      
  • C
  •   
  • 由f
  • 创建的对象   
  • 作为参数传递给f
  • 的对象   
  • 保存在C
  • 的实例变量中的对象   
     

该方法不应调用任何允许函数返回的对象的方法。换句话说,与朋友交谈,而不是与陌生人交谈。

Bob叔叔举了一个LoD违规的例子:

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
  

这是否违反Demeter取决于ctxt,Options和ScratchDir是对象还是数据结构。如果它们是对象,那么它们的内部结构应该隐藏而不是暴露,因此对其内部的了解明显违反了得墨忒耳法则。另一方面,如果ctxt,Options和ScratchDir只是没有行为的数据结构,那么它们会自然地暴露它们的内部结构,因此Demeter不适用。

     

使用访问器功能会使问题混乱。如果代码编写如下,那么我们可能不会询问Demeter违规行为。

final String outputDir = ctxt.options.scratchDir.absolutePath;

所以这可能是你的同事来自的地方。我认为“我们必须这样做因为LoD”这个论点充其量是不精确的。中心问题不是LoD,而是API是由对象还是数据结构组成。当有更多紧迫的事情要做时,这似乎是一种不必要的,容易出错的变化。

答案 1 :(得分:13)

在我看来,此更改与Law of Demeter无关。从本质上讲,法则是通过让方法调用整个其他对象链来将对象图的结构编码到代码中。例如,假设在汽车保险应用中,客户有策略,策略有车辆,车辆有分配给他们的司机,司机有出生日期,因此有年龄。您可以想象以下代码:

public boolean hasUnderageDrivers(Customer customer) {
    for (Vehicle vehicle : customer.getPolicy().getVehicles()) {
        for (Driver driver : vehicle.getDrivers()) {
            if (driver.getAge() < 18) {
                return true;
            }
        }
    }
    return false;
}

这违反了得墨忒耳法则,因为此代码现在具有内部知识,而且不需要知道。它知道司机被分配到车辆,而不是仅仅被分配到整个保险单。如果将来保险公司决定司机只是在保单上,而不是分配给特定的车辆,那么这段代码就必须改变。

问题在于它调用了参数getPolicy()的方法,然后是另一个getVehicles(),然后是另一个getDrivers(),然后是另一个getAge() 。德米特定律说,类的方法只应该调用方法:

  • 本身
  • 其字段
  • 其参数
  • 它创建的对象

(最后一个可能是单元测试的问题,你可能希望工厂注入或创建对象而不是直接在本地创建,但这与Demeter法则无关。)

要解决hasUnderageDrivers()问题,我们可以传递Policy对象,我们可以在Policy上有一个知道如何确定策略是否包含未成年驱动程序的方法:

public boolean hasUnderageDrivers(Policy policy) {
    return policy.hasUnderageDrivers();
}

调低一级,customer.getPolicy().hasUnderageDrivers(),可能没问题 - 得墨忒耳法则是一个经验法则,而不是一个严格的规则。你也可能不必担心不太可能改变的事情; Driver可能总是会继续有一个出生日期和getAge()方法。

但回到你的情况,如果我们用公共字段替换所有这些getter会发生什么?它根本不符合得墨忒耳法。您仍然可以遇到与第一个示例中完全相同的问题。考虑:

public boolean hasUnderageDrivers(Customer customer) {
    for (Vehicle vehicle : customer.policy.vehicles) {
        for (Driver driver : vehicle.drivers) {
            if (driver.age < 18) {
                return true;
            }
        }
    }
    return false;
}

(我甚至将driver.getAge()转换为driver.age,尽管这可能是基于出生日期而非简单字段的计算。)

请注意,当我们使用公共字段而不是getter编写代码时,存在完全相同的问题,即嵌入对象图如何组合在一起(客户有一个具有驱动程序的车辆的策略)。这个问题与这些碎片的组合方式有关,而不是与是否正在调用吸气剂有关。

顺便提一下,喜欢getter(最终?)公共字段的正常理由是你可能需要稍后将一些逻辑放在后面。年龄被替换为基于出生日期和今天的日期的计算,或者设置者需要进行一些验证(例如,如果你通过null则抛出)与之相关联。我之前没有听说过在这种背景下引用的德米特定律。

答案 2 :(得分:0)

Demeter关于使用对象的法律,而不是数据结构,在我的理解DTO中。

Demeter法解释说你可以调用以下对象的方法:

  1. 作为参数传递
  2. 在方法<}中本地清除
  3. 实例变量(对象&#39; s字段)
  4. 全局
  5. 数据模型表示其中包含一些应显示在外部的数据的容器。它是他们的角色,除此之外他们还没有其他一些行为。