我已经阅读了几乎所有标记为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是什么。我肯定能看到这个问题,但我不确定如何避免它。特别是当任何一个班级打电话给他们时,可能知道他们想要为相关客户的“主要”地址提取“移动”电话号码。
如何根据得墨忒耳法对其进行重构,为什么这样做会很好?
答案 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...
}
}