13

我已经阅读了几乎所有标记为 Law-of-Demeter 的问题。我的具体问题在任何其他问题中都没有得到回答,尽管它非常相似。我的主要问题是,当您有一个具有多层组合的对象,但需要从各种对象中检索属性值时,您如何实现这一点以及为什么采用一种方法而不是另一种方法?

假设您有一个由其他对象组成的非常标准的对象,如下所示:

public class Customer {
  private String name;
  private ContactInfo primaryAddress;
  private ContactInfo workAddress;
  private Interests hobbies;
  //Etc...

  public getPrimaryAddress() { return primaryAddress; }
  public getWorkAddress() { return workAddress; }
  public getHobbies() { return hobbies; }
  //Etc...
}

private ContactInfo {
  private String phoneNumber;
  private String emailAddress;
  //Etc...

  public getPhoneNumber() { return phoneNumber; }
  public getEmailAddress() { return emailAddress; }
  //Etc...
}

private Interests {
  private List listOfInterests;
}

以下都违反了得墨忒耳法则:

System.out.println("Phone: " + customer.getPrimaryAddress().getPhoneNumber());
System.out.println("Hobbies: " + customer.getHobbies().getListOfInterests().toString());

我认为这也违反了得墨忒耳法则(澄清?):

ContactInfo customerPrimaryAddress = customer.getPrimaryAddress();
System.out.println("Phone: " + customerPrimaryAddress.getPhoneNumber());

因此,据推测,您将向 Customer 添加一个“getPrimaryPhoneNumber()”方法:

public getPrimaryPhoneNumber() {
  return primaryAddress.getPhoneNumber();
}

然后简单地调用: System.out.println("Phone: " + customer.getPrimaryPhoneNumber());

但随着时间的推移,这样做似乎实际上会带来很多问题,并且违背了得墨忒耳法则的意图。它使 Customer 类变成了一个巨大的 getter 和 setter 包,它们对自己的内部类有太多的了解。例如,似乎有朝一日 Customer 对象可能会有不同的地址(不仅仅是“主要”地址和“工作”地址)。甚至 Customer 类也可能只是有一个 ContactInfo 对象的列表(或其他集合),而不是特定命名的 ContactInfo 对象。在这种情况下,你如何继续遵循得墨忒耳法则?这似乎违背了抽象的目的。例如,在客户有一个 ContactInfo 项目列表的情况下,这似乎是合理的:

Customer.getSomeParticularAddress(addressType).getPhoneNumber();

当您想到某些人拥有手机和固定电话时,这似乎会变得更加疯狂,然后 ContactInfo 必须拥有电话号码的集合。

Customer.getSomeParticularAddress(addressType).getSomePhoneNumber(phoneType).getPhoneNumber();

在这种情况下,我们不仅指的是对象内的对象内的对象,而且我们还必须知道有效的地址类型和电话类型是什么。我绝对可以看到这个问题,但我不知道如何避免它。特别是当任何班级打电话给这个时,可能确实知道他们想要为相关客户的“主要”地址提取“移动”电话号码。

这怎么能被重构以符合得墨忒耳法则,为什么会这样呢?

4

3 回答 3

2

根据我的经验,显示的Customer示例不是“由其他对象组成的标准对象”,因为该示例采取了将其组成部分实现为内部类的附加步骤,并且进一步将这些内部类设为私有。这不是一件坏事。

一般来说,私有访问修饰符会增加信息隐藏,这是得墨忒耳法则的基础。暴露私人课程是矛盾的。NetBeans IDE 实际上包含一个针对“通过公共 API 导出非公共类型”的默认编译器警告。

我会断言,将私有类暴露在其封闭类之外总是不好的:它减少了信息隐藏并违反了得墨忒耳法则。因此,要回答有关返回外部实例的澄清问题ContactInfoCustomer是的,这是违规行为。

getPrimaryPhoneNumber()添加方法的建议解决方案Customer是一个有效的选项。困惑就在这里:“客户……对自己的内部类了解太多了。” 那是不可能的;这就是为什么这个例子不是一个标准的组合例子很重要。

封闭类具有任何嵌套类的 100% 知识。总是。无论这些嵌套类如何在封闭类(或其他任何地方)中使用。这就是为什么封闭类可以直接访问其嵌套类的私有字段和方法的原因:封闭类天生就知道它们的一切,因为它们是在其中实现的。

给定一个类 Foo 的荒谬示例,它有一个嵌套类 Bar,它有一个嵌套类 Baz,它有一个嵌套类 Qux,它不会违反 Demeter for Foo(内部)调用 bar.baz.qux 。方法()。Foo 已经知道关于 Bar、Baz 和 Qux 的一切;因为他们的代码在 Foo 内部,所以没有通过长方法链传递额外的知识。

然后,根据得墨忒耳法则,解决方案是Customer不返回中间对象,无论其内部实现如何;即是否Customer使用多个嵌套类实现或不使用,它应该只返回其客户端类最终需要的内容。

例如,最后一个代码片段可能被重构为, customer.getPhoneNumber(addressType, phoneType);

或者如果只有少数选项, customer.getPrimaryMobilePhoneNumber();

这两种方法都会导致Customer类的用户不知道其内部实现,并确保这些用户不必通过他们不直接感兴趣的对象进行调用。

于 2015-06-28T20:22:47.880 回答
1

重要的是要记住,尽管得墨忒耳法则 一个指导方针,而不是实际的法则。我们需要在更深的层次上检查它的目的,以确定在这里做什么是正确的。

得墨忒耳法则的目的是防止外部物体能够访问另一个物体的内部。访问内部有两个问题:1)它提供了太多关于对象内部结构的信息,2)它还允许外部对象修改类的内部。

对此问题的正确回应是将 Customer 方法返回的对象从内部表示中分离出来。换句话说,我们没有返回对 ContactInfo 类型的私有内部对象的引用,而是定义了一个新类 UnmodifiableContactInfo,并让 getPrimaryAddress 返回 UnmodifiableContactInfo,创建它并根据需要进行填充。

这让我们都受益于得墨忒耳法则。返回的对象不再是 Customer 的内部对象,这意味着 Customer 可以随意修改其内部存储,并且我们对 UnmodifiableContactInfo 所做的任何操作都不会影响 Customer 的内部。

(实际上我会重命名内部类并将外部类保留为 ContactInfo,但这是一个小问题)

所以这实现了得墨忒耳法则的目标,但看起来我们仍然在打破它。我的想法是 getAddress 方法不是返回一个 ContactInfo 对象,而是实例化它。这意味着在 Demeter 规则下,我们可以访问 ContactInfo 的方法,并且您上面编写的代码并不违反。

当然,您必须注意,尽管在访问 Customer 的代码中发生了“违反 Demeter 定律”,但需要在 Customer 中进行修复。一般来说,修复是一件好事 - 提供对内部对象的访问是不好的,无论是否使用多个“点”访问它们。

一些笔记。很明显,过于严格地应用得墨忒耳定律会导致白痴,例如:

int nameLength = myObject.getName().length()

这是我们大多数人每天都在做的技术违规。大家也这样做:

mylist.get(0).doSomething();

这在技术上是违规行为。但现实情况是,除非我们实际上允许外部代码基于检索到的对象影响主对象(客户)的行为,否则这些都不是问题。

概括

这是您的代码应如下所示:

public class Customer {
    private class InternalContactInfo {
        public ContactInfo createContactinfo() {
            //creates a ContactInfo based on its own values...
        }
        //Etc....
    }
   private String name;
   private InternalContactInfo primaryAddress;
   //Etc...

   public Contactinfo getPrimaryAddress() { 
      // copies the info from InternalContactInfo to a new object
      return primaryAddress.createContactInfo();
   }
   //Etc...
}

public class ContactInfo {
   // Not modifiable
   private String phoneNumber;
   private String emailAddress;
   //Etc...

   public getPhoneNumber() { return phoneNumber; }
   public getEmailAddress() { return emailAddress; }
   //Etc...
}

}

于 2015-07-27T18:10:38.083 回答
0

如何创建另一个名称为CustomerInformationProvider. 您可以将您的Customer对象作为构造函数参数传递给他的新类。然后你可以在这个类中编写所有获取电话、地址等的具体方法,同时保持Customer类干净。

于 2015-06-29T17:09:39.517 回答