带有数据模型对象的德墨忒尔法则[英] Law of Demeter with data model objects

本文是小编为大家收集整理的关于带有数据模型对象的德墨忒尔法则的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

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

为了促进我们遵守Demeter定律:一个模块不应该知道它操纵的"对象"的内部.由于数据 结构不包含行为,它们自然会暴露其内部结构.因此,在这种情况下,Demeter不适用.

我承认我必须掌握对LOD的了解,但是对于我的一生,我找不到任何东西可以表明这是在法律的精神之内.我们的模型中没有一个getters/setter都包含任何业务逻辑,这是他这样做的理由,因此这些对象的客户不必了解在get/set方法中是否执行某些业务逻辑.<<<<<<<

我认为这是对需要"对象结构的内部知识"的含义,或者至少是从字面上看,并在此过程中打破了一个相当标准的惯例.

所以我的问题是,直接以lod的名称直接揭示模型对象内部结构而不是通过getters/setter?

?

推荐答案

有一本书,罗伯特·马丁(Robert Martin)涵盖了这一点.

在第6章(对象和数据结构)中,他谈到了对象和数据结构之间的基本差异. 对象受益于封装,数据结构不会.

有一个关于Demeter定律的部分:

有一个著名的启发式方法,称为 demeter的律法了解操纵物体的内部.正如我们在上一部分中看到的那样,对象隐藏了他们的数据并公开操作.这意味着一个对象不应通过访问者暴露其内部结构,因为这样做是要暴露而不是隐藏其内部结构.

更确切地说,Demeter的定律说,C类的方法F仅应调用这些方法:

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

该方法不应在任何允许功能返回的对象上调用方法.换句话说,与朋友交谈,而不是与陌生人交谈.

鲍勃叔叔给出了违反LOD的例子:

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

这是否违反了Demeter,取决于CTXT,选项和SCRATCHDIR是否是对象或数据结构.如果它们是对象,那么他们的内部结构应该被隐藏而不是暴露,因此对他们的内部的知识显然违反了Demeter定律.另一方面,如果CTXT,选项和SCRATCHDIR只是没有行为的数据结构,那么它们自然会暴露其内部结构,因此Demeter不适用.

使用登录机功能使问题困惑.如果代码的编写如下,那么我们可能不会询问违反塞米特的行为.

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

因此,这可能是您的同事来自的地方.我认为论点"我们必须这样做,因为LOD"充其量是不精确的.中心问题并不是API是否由对象或数据结构组成.当有更多紧迫的事情要做时,这似乎确实是不必要的,易错的变化.

其他推荐答案

在我看来,这种变化与 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;
}

这将违反Demeter的法律,因为该代码现在已经了解了它不需要知道的内部设备.它知道驾驶员被分配给车辆,而不仅仅是被分配给整个保险单.如果将来,保险公司决定将驾驶员只是在政策中,而不是被分配给特定车辆,那么该代码将不得不更改.

问题在于它调用了其参数的方法,getPolicy(),然后调用另一个getVehicles(),然后是另一个,然后是另一个,getDrivers(),然后是另一个,getAge(). Demeter的定律说,班级的方法只能在:

上调用方法:

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

(最后一个可能是单位测试的问题,您可能希望将对象注入或由工厂注入或创建,而不是直接在本地创建,但这与Demeter定律无关.)

要使用hasUnderageDrivers()解决该问题,我们可以通过Policy对象,我们可以在Policy上使用一种方法,该方法知道如何确定该策略是否有未成年驱动程序:

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

将一个级别调低,customer.getPolicy().hasUnderageDrivers()可能还可以 - 塞默特定律是经验法则,而不是艰难的规则.您也可能不必担心不太可能改变的事情. Driver可能总是会继续有生育日期和getAge()方法.

但是,返回您的案件,如果我们用公共领域代替所有这些getters怎么办?它根本没有帮助Demeter的法律.您仍然可以遇到与第一个示例中完全相同的问题.考虑:

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,尽管这可能是基于出生日期而不是简单领域的计算.)

请注意,嵌入对象图如何组合在一起的知识完全相同(客户具有具有驱动程序的车辆的策略),当我们用公共字段而不是getters编写代码时,存在.问题与零件的组合方式有关,而不是与被称为getters的方式有关.

顺便说一句,更喜欢Getters(最终?)公共领域的正常原因是您可能需要在以后将一些逻辑放在后面.年龄由基于出生日期和今天日期的计算替换,或者建立者需要进行一些验证(例如,如果您通过null,则可以抛出).我以前从未听过Demeter的律法.

其他推荐答案

Demeter的定律关于使用对象而不是数据结构,在您的情况下,我了解的是DTO.

Demeter定律解释说您可以调用:

的对象方法
  1. 通过参数
  2. 通过
  3. 该方法内的本地切片
  4. 实例变量(对象的字段)
  5. 全球

数据模型代表包含一些数据的容器,这些数据应在外面显示.这是他们的角色,除此之外,他们没有其他行为.

本文地址:https://www.itbaoku.cn/post/627684.html

问题描述

I came back to work from vacation yesterday, and in our daily standup, my teammates mentioned they were refactoring all of the model objects in our java code to remove all getters and setters and make the model fields all public objects instead, invoking the Law of Demeter as the reason for doing so because

to facilitate the our adherence to Demeter's law: a module should not know about the innards of the 'objects' it manipulates. Since data structures contain no behavior, they naturally exposes their internal structure. So in that case, Demeter does not apply.

I admit I had to brush up on my knowledge of the LoD, but for the life of me I can't find anything to indicate that this is within the spirit of the law. None of the getters/setters in our models contain any business logic, which is his justification for doing this so clients of these objects don't have to understand whether or not there is some business logic being executed within the get/set methods.

I think this is a misunderstanding of what it means to need 'internal knowledge of an objects structure', or at the very least is taking it too literally and breaking a pretty standard convention in the process.

So my question is, does it actually make any sense to expose model objects internal structure directly instead of via getters/setters in the name of the LoD?

推荐答案

There is a book called Clean Code by Robert Martin that covers this.

In Chapter 6 (Objects and Data Structures) he talks about fundamental differences between objects and data structures. Objects benefit from encapsulation, data structures don't.

There is a section about the Law of Demeter:

There is a well-known heuristic called the Law of Demeter that says a module should not know about the innards of the objects it manipulates. As we saw in the last section, objects hide their data and expose operations. This means that an object should not expose its internal structure through accessors because to do so is to expose, rather than to hide, its internal structure.

More precisely, the Law of Demeter says that a method f of a class C should only call the methods of these:

  • C
  • An object created by f
  • An object passed as an argument to f
  • An object held in an instance variable of C

The method should not invoke methods on objects that are returned by any of the allowed functions. In other words, talk to friends, not to strangers.

Uncle Bob gives an example of a LoD violation:

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

Whether this is a violation of Demeter depends on whether or not ctxt, Options, and ScratchDir are objects or data structures. If they are objects, then their internal structure should be hidden rather than exposed, and so knowledge of their innards is a clear violation of the Law of Demeter. On the other hand, if ctxt, Options, and ScratchDir are just data structures with no behavior, then they naturally expose their internal structure, and so Demeter does not apply.

The use of accessor functions confuses the issue. If the code had been written as follows, then we probably wouldn’t be asking about Demeter violations.

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

So this is probably where your co-workers are coming from. I think the argument "we have to do this because LoD" is imprecise at best. The central issue isn't LoD so much as whether the API consists of objects or data structures. It does seem like an unnecessary and error-prone change to push through when there are more pressing things to do.

其他推荐答案

It doesn't seem to me that this change has anything to do with the Law of Demeter. The law is, essentially, about encoding the structure of your object graph into your code by having methods call through an entire chain of other objects. For example, suppose, in a car insurance application, that a customer has a policy, and a policy has vehicles, and vehicles have drivers assigned to them, and drivers have birth dates and, thus ages. You could imagine the following code:

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

This would violate the Law of Demeter because this code now has knowledge of internals that it doesn't need to know. It knows that drivers are assigned to vehicles, instead of just being assigned to the insurance policy as a whole. If, in the future, the insurance company decided that drivers would simply be on the policy, instead of being assigned to particular vehicles, then this code would have to change.

The problem is that it calls a method of its parameter, getPolicy(), and then another, getVehicles(), and then another, getDrivers(), and then another, getAge(). The Law of Demeter says that a method of a class should only call methods on:

  • Itself
  • Its fields
  • Its parameters
  • Objects it creates

(The last one can be a problem for unit testing, where you may want to have objects injected or created by factories rather than created directly locally, but that's not relevant to the Law of Demeter.)

To fix the problem with hasUnderageDrivers() we can pass in the Policy object and we can have a method on Policy that knows how to determine whether the policy has underage drivers:

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

Calling one level down, customer.getPolicy().hasUnderageDrivers(), is probably okay — the Law of Demeter is a rule of thumb, not a hard-and-fast rule. You also probably don't have to worry much about things that aren't likely to change; Driver is probably always going to continue to have a birth date and a getAge() method.

But returning to your case, what happens if we replace all these getters with public fields? It doesn't help with the Law of Demeter at all. You can still have the exact same problem as in the first example. Consider:

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

(I've even converted driver.getAge() to driver.age, although that would probably be a calculation based on the birth date rather than a simple field.)

Notice that the exact same problem with embedding knowledge of how the object graph is put together (a customer has a policy which has vehicles which have drivers) is present when we write the code with public fields instead of getters. The problem has to do with how the pieces are put together, not with whether getters are being called.

Incidentally, the normal reason to prefers getters over (final?) public fields is that you may need to put some logic behind them later on. An age is replaced with a calculation based on the birth date and today's date, or a setter needs to have some validation (throws if you pass null, for instance) associated with it. I haven't heard the Law of Demeter invoked in that context before.

其他推荐答案

The Law of Demeter about the working with objects, not data structure, in your case DTO as I understand.

The Law of Demeter explains that you can call methods of objects that are:

  1. Passed as arguments
  2. Cleated locally inside the method
  3. Instance variables (object's fields)
  4. Global

Data models represent containers with some data inside them that should be shown outside. It's their role and they haven't some other behavior besides this.