得墨忒耳定律和收藏组合如何协同工作?

时间:2013-03-28 21:23:29

标签: java oop law-of-demeter

我已经阅读了几乎所有标记为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());

因此,您可能会向客户添加“getPrimaryPhoneNumber()”方法:

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

然后简单地打电话:     System.out.println(“电话:”+ customer.getPrimaryPhoneNumber());

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

Customer.getSomeParticularAddress(addressType).getPhoneNumber();

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

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

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

如何根据得墨忒耳法对其进行重构,为什么这样做会很好?

3 个答案:

答案 0 :(得分:2)

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

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

我认为在其封闭类之外暴露私有类总是坏的:它减少了信息隐藏并违反了得墨忒耳定律。因此,要回答关于在ContactInfo之外返回Customer的实例的澄清问题:是的,这是违规行为。

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

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

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

根据Demeter法则,解决方案是Customer不返回中间对象,无论其内部实现如何;即,Customer是使用多个嵌套类实现还是无实现,它应仅返回其客户端类最终需要的内容。

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

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

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

答案 1 :(得分:0)

如何使用CustomerInformationProvider之类的名称创建另一个类。您可以将Customer对象作为构造函数参数传递给他的新类。然后你可以编写所有具体方法来获取这个课程中的手机,地址等,同时让Customer课程保持清洁。

答案 2 :(得分:0)

重要的是要记住得墨忒耳法 ,尽管它的名称是指南,而不是实际的法律。我们需要在更深层次上检查其目的,以确定在这里做什么是正确的。

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

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

这让我们得到了得墨忒耳法的好处。返回的对象不再是Customer的内部对象,这意味着Customer可以根据需要修改其内部存储,并且我们对UnmodifiableContactInfo所做的任何操作都不会影响Customer的内部。

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

因此,这实现了德米特定律的目标,但看起来我们仍然打破它。我想到的方式是getAddress方法不返回ContactInfo对象,而是实例化它。这意味着在Demeter规则下我们可以访问ContactInfo的方法,并且您在上面编写的代码不是违规行为。

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

一些笔记。显而易见的是,过度严格地应用得墨忒耳定律导致了愚蠢,例如禁止:

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

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

mylist.get(0).doSomething();

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

摘要

以下是您的代码应该是什么样的:

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...
}

}