Java 数据模型对象的德米特定律

Java 数据模型对象的德米特定律,java,design-patterns,law-of-demeter,Java,Design Patterns,Law Of Demeter,昨天我度假回来工作,在我们的日常对话中,我的队友提到他们正在重构java代码中的所有模型对象,以删除所有getter和setter,并将模型字段改为所有公共对象,调用Demeter定律作为这样做的原因,因为 为了便于我们遵守德米特定律:模块不应该知道它操纵的“对象”的内部。自数据 结构不包含任何行为,它们自然地暴露其内部结构。因此,在这种情况下,Demeter不适用 我承认我不得不重新学习我的LoD知识,但就我的一生而言,我找不到任何东西表明这符合法律精神。我们模型中的getter/setter

昨天我度假回来工作,在我们的日常对话中,我的队友提到他们正在重构java代码中的所有模型对象,以删除所有getter和setter,并将模型字段改为所有公共对象,调用Demeter定律作为这样做的原因,因为

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

我承认我不得不重新学习我的LoD知识,但就我的一生而言,我找不到任何东西表明这符合法律精神。我们模型中的getter/setter都不包含任何业务逻辑,这是他这样做的理由,这样这些对象的客户端就不必了解get/set方法中是否有一些业务逻辑正在执行

我认为这是对需要“对象结构的内部知识”意味着什么的误解,或者至少是太过字面化,在这个过程中打破了一个相当标准的惯例


所以我的问题是,直接公开模型对象的内部结构而不是以LoD的名义通过getter/setter公开模型对象的内部结构是否有意义?

罗伯特·马丁(Robert Martin)的一本名为《干净的代码》(Clean Code)的书对此进行了介绍

在第6章(对象和数据结构)中,他讨论了对象和数据结构之间的基本区别。 对象受益于封装,而数据结构则不然

有一节是关于得墨忒尔法的:

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

更准确地说,德米特定律说,C类的方法f应该只调用以下方法:

  • C
  • 由f创建的对象
  • 作为参数传递给f的对象
  • 保存在C的实例变量中的对象
该方法不应调用任何允许函数返回的对象上的方法。换句话说,与朋友交谈,而不是陌生人

Bob叔叔给出了LoD违规的示例:

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
这是否违反了Demeter,取决于ctxt、Options和ScratchDir是否为对象或数据结构。如果它们是物体,那么它们的内部结构应该隐藏起来,而不是暴露出来,因此了解它们的内脏显然违反了得墨忒尔定律。另一方面,如果ctxt、Options和ScratchDir只是没有行为的数据结构,那么它们自然会暴露其内部结构,因此Demeter不适用

访问器函数的使用混淆了这个问题。如果代码是按如下方式编写的,那么我们可能就不会询问Demeter违规的情况


所以这可能就是你的同事的来源。我认为“我们必须这样做,因为LoD”的论点充其量是不准确的。核心问题不是LoD,而是API是否由对象或数据结构组成。当有更紧迫的事情要做时,这看起来确实是一个不必要的、容易出错的更改。

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

public boolean hasUnderageDrivers(Customer customer) {
    for (Vehicle vehicle : customer.getPolicy().getVehicles()) {
        for (Driver driver : vehicle.getDrivers()) {
            if (driver.getAge() < 18) {
                return true;
            }
        }
    }
    return false;
}
向下调用一个级别,
customer.getPolicy().hasUnderageDrivers()
,可能没问题——德米特定律是一条经验法则,而不是一条硬性法则。你也可能不必太担心那些不太可能改变的事情<代码>驱动程序可能总是会有一个出生日期和一个
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;
}
public boolean hasUnderageDrivers(客户){
用于(车辆:客户。政策。车辆){
用于(驾驶员:车辆.驾驶员){
如果(驾驶员年龄<18岁){
返回true;
}
}
}
返回false;
}
(我甚至将
driver.getAge()
转换为
driver.age
,尽管这可能是基于出生日期而不是简单字段的计算。)

请注意,当我们使用公共字段而不是getter编写代码时,在嵌入对象图如何组合的知识(客户有一个策略,该策略包含有司机的车辆)时,会出现完全相同的问题。问题在于如何组合这些片段,而不是是否调用getter


顺便说一句,与(final?)公共字段相比,更喜欢getter的正常原因是,您可能需要在后面添加一些逻辑。年龄将替换为基于出生日期和今天日期的计算,或者setter需要与之关联的一些验证(例如,如果通过
null
,则抛出)。我以前从未听说过在这种情况下引用德米特定律。

德米特定律是关于使用对象而不是数据结构的
public boolean hasUnderageDrivers(Policy policy) {
    return policy.hasUnderageDrivers();
}
public boolean hasUnderageDrivers(Customer customer) {
    for (Vehicle vehicle : customer.policy.vehicles) {
        for (Driver driver : vehicle.drivers) {
            if (driver.age < 18) {
                return true;
            }
        }
    }
    return false;
}