得墨忒耳法则不会阻止将对象传递给类构造函数。但是,它确实禁止稍后取回相同的对象并在其上调用方法以获取标量值。相反,应该创建一个返回标量值的代理方法。我的问题是,为什么将一个对象传递给类构造函数是可以接受的,但以后再取回同一个对象并从中提取一个值是不可接受的?
3 回答
因为得墨忒耳定律说你不应该设计一个对象的外部接口,让它看起来好像是由具有已知接口的某些其他对象组成的,客户可以抓住和访问。
您将一个对象传递给构造函数以告诉您的新对象如何表现,但是该对象是否保留该参数对象,或者保留它的副本,或者只是查看它一次并忘记它曾经存在过,这与您无关. 通过拥有一个 getMyParameterBack 方法,您已经承诺所有未来的实现能够按需生成整个对象,并且所有客户端都与两个接口而不是一个接口耦合。
例如,如果您将 URL 参数传递给 HTTPRequest 对象的构造函数,那么这并不意味着 HTTPRequest 应该有一个 getURL 方法,该方法返回一个 URL 对象,然后调用者将在该对象上调用 getProtocol、getQueryString 等。如果有人拥有 HTTPRequest 对象的人可能想知道请求的协议,他们应该(法律规定)通过在他们拥有的对象上调用 getProtocol 来找出答案,而不是在他们碰巧知道 HTTPRequest 正在内部存储的其他对象上。
这个想法是为了减少耦合 - 如果没有 Demeter 法则,用户必须知道 HTTPRequest和URL 的接口才能获得协议。有了法律,他们只需要HTTPRequest的接口。并且 HTTPRequest.getProtocol() 显然可以返回“http”,而不需要一些 URL 对象参与讨论。
有时请求对象的用户恰好是创建它的人,因此也使用 URL 接口来传递参数这一事实既不存在也不存在。并非所有 HTTPRequest 对象的用户都会自己创建它们。因此,根据法律有权访问 URL 因为他们自己创建 URL 的客户可以这样做,而不是从请求中获取它。没有创建 URL 的客户端不能。
就我个人而言,我认为通常以简单形式陈述的得墨忒耳法则已经破解。他们是否认真地说,如果我的对象有一个字符串 Name 字段,并且我想知道 Name 是否包含任何非 ASCII 字符,那么我必须在我的对象上定义一个 NameContainsNonASCIICharacters 方法而不是查看字符串本身,或者向采用回调函数的类添加一个 visitName 函数,以便通过确保字符串是我编写的函数的参数来解决限制?这根本不会改变耦合,它只是用访问者方法替换了 getter 方法。如果我想操纵返回值,是否每个返回整数的类都有一整套算术运算?getPriceMultipliedBy(int n)? 肯定不是。
它的用处在于,当你破坏它时,你可以问自己为什么要破坏它,以及是否可以通过不破坏它来设计更好的界面。通常你可以,但实际上这取决于你在谈论什么类型的对象。某些接口可以安全地与大量代码耦合——比如整数、字符串,甚至 URL,它们代表了广泛使用的概念。
这个想法是你只和你的直接朋友交谈。所以,你不要这样做...
var a = new A();
var x = a.B.doSomething();
相反,你这样做......
var a = new A();
var x = a.doSomething(); // where a.doSomething might call b.doSomething();
它有它的优点,因为对于调用者来说事情变得更简单了(Car.Start() 与 Car.Engine.Start()),但是你会得到很多小的包装方法。您还可以使用中介者模式来减轻这种类型的“违规”。
JP的回答还不错,所以这只是一个补充,不是异议或者其他替代。
我理解这种启发式的方式是,对 A 的调用不应该因为 B 类的变化而中断。所以如果你用 abfoo() 链接你的调用,那么 A 的接口就会依赖于 B 的接口,这违反了规则。相反,您应该调用 a.BFoo(),它会为您调用 b.foo()。
这是一个很好的经验法则,但它可能导致笨拙的代码并没有真正解决依赖关系,而是将其奉为圣殿。现在 A 必须永远提供 BFoo,即使 B 不再提供 Foo。没有太大的改进,如果对 B 的更改破坏了想要 Foo 而不是 B 本身的调用者,那么至少在某些情况下可能会更好。
我还要补充一点,严格来说,对于某些普遍存在的类,例如字符串,这条规则经常被打破。决定哪些类在应用程序的特定层中同样普遍存在并随意忽略 Demeter 对它们的“规则”也许是可以接受的。