【发布时间】:2009-07-20 01:20:10
【问题描述】:
Law of Demeter 不会阻止将对象传递给类构造函数。但是,它确实禁止稍后取回相同的对象并在其上调用方法以获取标量值。相反,应该创建一个返回标量值的代理方法。我的问题是,为什么将一个对象传递给类构造函数是可以接受的,但以后再取回同一个对象并从中提取一个值是不可接受的?
【问题讨论】:
-
你能提供“直径定律”的链接吗?
标签: oop law-of-demeter
Law of Demeter 不会阻止将对象传递给类构造函数。但是,它确实禁止稍后取回相同的对象并在其上调用方法以获取标量值。相反,应该创建一个返回标量值的代理方法。我的问题是,为什么将一个对象传递给类构造函数是可以接受的,但以后再取回同一个对象并从中提取一个值是不可接受的?
【问题讨论】:
标签: oop law-of-demeter
因为得墨忒耳定律说,您不应该设计一个对象的外部接口,让它看起来好像由具有已知接口的某些其他对象组成,客户可以抓住并访问。
您将一个对象传递给构造函数以告诉您的新对象如何表现,但是该对象是否保留该参数对象,或者保留它的副本,或者只是查看一次就忘记了,这与您无关它曾经存在过。通过使用 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,它们代表了广泛使用的概念。
【讨论】:
Foo.Bar 是一个值,那么Foo.Bar.Boz 甚至Foo.Bar.Biz.Bam.Boz 都可以(假设中间的东西都是值)。但是,如果Foo.Bar 的类型是一个实体,那么Foo.Bar.Boz 就会变得模糊,因为不清楚该实体应该如何与Foo 绑定。
JP的回答还不错,所以这只是一个补充,而不是不同意或其他替代。
我理解这种启发式的方式是,对 A 的调用不应该因为 B 类的变化而中断。所以如果你用 a.b.foo() 链接你的调用,那么 A 的接口就会依赖于 B 的接口,这违反了规则。相反,你应该调用 a.BFoo(),它会为你调用 b.foo()。
这是一个很好的经验法则,但它可能会导致笨拙的代码并没有真正解决依赖关系,而是体现它。现在 A 必须永远提供 BFoo,即使 B 不再提供 Foo。没有太大的改进,如果对 B 的更改破坏了需要 Foo 而不是 B 本身的调用者,至少在某些情况下可能会更好。
我还要补充一点,严格来说,对于某些普遍存在的类,例如字符串,这条规则经常被打破。决定哪些类在应用程序的特定层中同样普遍存在并随意忽略 Demeter 的“规则”也许是可以接受的。
【讨论】:
这个想法是您只与您的直接朋友交谈。所以,你不要这样做......
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()),但你会得到很多小的包装方法。您也可以使用Mediator pattern 来缓解此类“违规行为”。
【讨论】: