【问题标题】:Law of Demeter and Class Constructors得墨忒耳定律和类构造函数
【发布时间】:2009-07-20 01:20:10
【问题描述】:

Law of Demeter 不会阻止将对象传递给类构造函数。但是,它确实禁止稍后取回相同的对象并在其上调用方法以获取标量值。相反,应该创建一个返回标量值的代理方法。我的问题是,为什么将一个对象传递给类构造函数是可以接受的,但以后再取回同一个对象并从中提取一个值是不可接受的?

【问题讨论】:

  • 你能提供“直径定律”的链接吗?

标签: oop law-of-demeter


【解决方案1】:

因为得墨忒耳定律说,您不应该设计一个对象的外部接口,让它看起来好像由具有已知接口的某些其他对象组成,客户可以抓住并访问。

您将一个对象传递给构造函数以告诉您的新对象如何表现,但是该对象是否保留该参数对象,或者保留它的副本,或者只是查看一次就忘记了,这与您无关它曾经存在过。通过使用 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,它们代表了广泛使用的概念。

【讨论】:

  • 看,Uri 包装器正是我可以免除此规则的那种无处不在的类,就像字符串一样。被视为达到目的的一种手段,这是一个很好的经验法则,但它本身并不是目的。目标是隐藏内部实现细节,而不是装模作样。
  • 是的,如果它被称为 Demeter 的指导方针,我根本没有任何争论,但是当试图 牧猫 建议程序员时,胆怯是没有用的。我毫不怀疑,作为一项学术练习,将其作为法律应用是有趣且富有成果的,并为改进界面设计提供了很多想法。 IMO 一旦您例行地将一个接口嵌入到另一个接口中,您就会划清界限。有时 X 实际上只有一个 Y,每个人都知道 :-)
  • 说得好。我对疯狂的猫典型程序员的担忧是,有些人倾向于将好的想法视为神奇。典型的例子是最近的毕业生,他阅读了 GoF 关于设计模式的书,然后开始将所有东西都变成单例。
  • 这是一个出色的答案!此外,这使一切变得更加清晰。谢谢! :)
  • 您的“名称包含非 ASCII 字符”说明了一个基本问题,与其说是 LOD,还不如说是许多 OOP 框架:entities。如果Foo.Bar 是一个值,那么Foo.Bar.Boz 甚至Foo.Bar.Biz.Bam.Boz 都可以(假设中间的东西都是值)。但是,如果Foo.Bar 的类型是一个实体,那么Foo.Bar.Boz 就会变得模糊,因为不清楚该实体应该如何与Foo 绑定。
【解决方案2】:

JP的回答还不错,所以这只是一个补充,而不是不同意或其他替代。

我理解这种启发式的方式是,对 A 的调用不应该因为 B 类的变化而中断。所以如果你用 a.b.foo() 链接你的调用,那么 A 的接口就会依赖于 B 的接口,这违反了规则。相反,你应该调用 a.BFoo(),它会为你调用 b.foo()。

这是一个很好的经验法则,但它可能会导致笨拙的代码并没有真正解决依赖关系,而是体现它。现在 A 必须永远提供 BFoo,即使 B 不再提供 Foo。没有太大的改进,如果对 B 的更改破坏了需要 Foo 而不是 B 本身的调用者,至少在某些情况下可能会更好。

我还要补充一点,严格来说,对于某些普遍存在的类,例如字符串,这条规则经常被打破。决定哪些类在应用程序的特定层中同样普遍存在并随意忽略 Demeter 的“规则”也许是可以接受的。

【讨论】:

  • 有点不得不在这里 +1 你因为你说的和我一样只是更短更快:-)
  • 我回馈了你的帮助,因为你提供了一个很好的例子。
【解决方案3】:

这个想法是您只与您的直接朋友交谈。所以,你不要这样做......

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 来缓解此类“违规行为”。

【讨论】:

  • 我以前从未听说过得墨忒耳定律,所以... 需要委托给 B 的便利函数是完全有道理的,但是“B getB()const”是否被禁止?如果有,为什么?
  • 这就是法律的全部含义……可以重述“您只与您的直系朋友交谈”。有什么好处?这个想法已经存在了很长时间。这些天来,我们正在编写非常松散耦合的应用程序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多