16

我在洗澡的时候想到了一些事情。

deferred / promise 模式是通过允许开发人员链接调用函数来减少回调地狱,如下所述:

Parse.User.logIn("user", "pass").then(function(user) {
  return query.find();
}).then(function(results) {
  return results[0].save({ key: value });
}).then(function(result) {
  // the object was saved.
});

从我的头顶上 - 如果我错了请纠正我 - 但似乎使用 deferred / promises 是打破 Demeter 法则的简单方法?

得墨忒耳法则规定:

对象的方法只能调用以下方法:

  • 对象本身。
  • 方法的参数。
  • 在方法中创建的任何对象。
  • 对象的任何直接属性/字段。

每个单元应该只对其他单元有有限的了解:只有与当前单元“密切”相关的单元。或者:每个单位应该只和它的朋友交谈;不要和陌生人说话。

对此有何评论?

2013 年 12 月 1 日更新:

我的问题的摘要版本。Promise 框架旨在简化异步编码并避免“回调地狱”。Promise 最有益的功能之一是您可以使用 链式调用事件.then(),如我上面的示例所示。

鉴于所有代码/函数现在都在使用 Promise(就像 Benjamin Gruenbaum(下面的作者)目前正在做的那样),它不会打开它以通过 go.then().then().then()等方式使链调用函数变得非常容易吗?

编写一个接一个调用函数的代码(.then().then().then())必须是如何打破得墨忒耳定律的教科书示例。

因此我的问题;Promise 框架是否促进/开放/使其更容易滥用/违反 Demeter 法则?

4

3 回答 3

10

我认为您误解了得墨忒耳定律的含义,以及它对 JavaScript 等语言和 Promise 等框架的适用性。

Promise 不是 Demeter 定律所设想的意义上的“单位”,它对应于诸如银行应用程序中的 Account 或 Customer 之类的“类”之类的东西。它们是用于异步控制流的更高级别的元结构。很难看出这样的元结构如何存在或有用,而不能够与它旨在控制的任意外部实体(非朋友)“交谈”。

得墨忒耳定律似乎高度关注经典的 OO 系统,其中一切都是类或方法。如前所述,它似乎不允许对传入函数进行任何调用,因此即使不是全部,也不允许调用大多数函数式编程。

另一种看待这个问题的方式是,如果你认为 Promise 违反了这条法律,那么回调当然也会这样做。毕竟,两者基本上是同构的——区别本质上是句法。因此,如果你一心想不违反得墨忒耳定律,你也将无法使用回调——那么你将如何编写最基本的异步程序呢?

于 2013-12-01T13:39:28.930 回答
8

简短的回答是肯定的。

我认为这已经变得比必要的复杂了,因为人们将这个问题与一些有趣的观点混淆了,这些观点与得墨忒耳法则没有直接关系。就像我们在谈论 JavaScript 一样。或者我们正在处理回调的事实。这些是不适用的细节。

让我们退后一步,重新讨论。软件工程的首要目标是尽可能地限制耦合。换句话说,当我更改代码时,我想确保这不会迫使我在周末工作以更改大量其他代码。得墨忒耳法则的存在是为了防止一种耦合——特征嫉妒。它通过对方法f可以用来完成其工作的形式提供正式限制来做到这一点。

OP @corgrath 很好地列举了这些限制。描述违反得墨忒耳定律的一个简单方法是:“你不能对允许的 4 个对象中的任何一个调用任何方法。”

现在终于到了@corgrath 提供的示例代码:

Parse.User.logIn("user", "pass").then(function(user) {
  return query.find();
}).then(function(results) {
  return results[0].save({ key: value });
}).then(function(result) {
  // the object was saved.
});

让我们称其Parse数据结构而不是对象(有关区别的更多信息,请参见鲍勃叔叔的绝妙著作Clean Code的第 6 章,这是我第一次接触得墨忒耳定律)。然后我们就可以了Parse.User

User显然是一个具有方法和行为的对象。这些方法之一是logIn。这会返回一个Promise. 一旦我们在这个物体上调用任何东西,我们就违反了德墨忒耳法则

而已。

顺便说一句,我还会很快提到 JavaScript 中的函数是对象。因此,得墨忒耳定律也适用于传递给每个then调用的回调函数。但在每个函数中,确实存在的函数方法都不会被调用,因此then调用不会违反得墨忒耳法则。

现在有趣的是,这种明显违反得墨忒耳法则的行为是否重要。软件工程是一门艺术。我们有各种各样的法律、原则和实践,但宗教上对它们的坚持与对它们的无知一样适得其反。尝试 100% 的代码覆盖率是愚蠢的;对 getter 和 setter 进行单元测试是愚蠢的;为 100% 的班级凝聚力而战是愚蠢的;创建 100% 的包稳定性是愚蠢的;等等

在这种情况下,我会说违反德墨忒耳法则无关紧要。Promise不以任何方式暴露内部;它们公开了用于执行另一个操作的抽象(在这种情况下,注册一个回调,但这与讨论无关)。换句话说,我是否必须通过打所有这些then电话来担心周末工作?可能性真的很低。我的意思是他们可能会将方法重命名为andThenor whenYoureReadyDoThis,但我对此表示怀疑。

这很重要,因为我喜欢我的周末。我不想做不必要的事情。我想做一些有趣的事情,比如在 Stack Overflow 上发布论文答案。

所以总结起来,有两个问题:

  • 代码是否Promise违反了得墨忒耳法则?是的。
  • 有关系吗?不

将两者混为一谈,将各种无关的信息带入讨论只会使事情变得混乱。

于 2013-12-07T22:37:49.670 回答
3

好问题!尽管它没有关注认为与 Promise 模式冲突的遵循 LoD 的方面 :) 但是,从您的评论中可以看出,您主要关注的是回调的链接和使用。

链接在模式的实现中很常见,但不会增加其语义;实际上它只是语法糖,使以下代码是同义词:

someAsyncPromise.then(/*do some stuff first*/).then(/*do other stuff*/);

someAsyncPromise.then(/*do some stuff first*/);
someAsyncPromise.then(/*do other stuff*/);

上面,then方法返回对原始承诺对象的引用,someAsyncPromise不是新的/不同的承诺对象。这可能违反了其他一些 OO 原则,但不违反 LoD,在这种情况下,对象调用其自己的方法是不道德的(d'oh :) 也许在常见的 jQuery 选择器链中更容易识别:

$('#element').hide().text('oh hi').css('color', 'fuchsia');
//synonymous to
$('#element').hide();
$('#element').text('oh hi');
$('#element').css('color', 'fuchsia');

(好吧,并不是真正的同义词,因为$()选择器会重新查询第二个示例中的元素;但是,如果我们已经缓存了 jQuery 对象,那就是这样!你明白了。

现在让我们关注回调。javascript 的美妙之处在于函数是一等公民,你可以传递它们。你的例子中的 noop 是做什么的?

function(result) {
  // the object was saved.
}

它只是坐在那里,就像一个等待孵化的鸡蛋。您正在使用函数表达式创建一个匿名函数,然后将其作为 的参数传递.then(),将其推送到要执行的承诺堆栈中。

因此,在某种程度上,对 Promise 模式的一种解释是将其视为遵守LoD的教科书示例

...

最后让我说当前的维基百科条目,恕我直言,极具误导性:

对于许多使用点作为字段标识符的现代面向对象语言,该法则可以简单地表述为“仅使用一个点”。也就是说,代码在没有a.b.Method()的地方违反了法律。a.Method()

洛尔乌特?这过于关注语法和外观,而不是底层结构和架构。有一个非常全面的解释,涵盖了两者之间的区别(以及维基百科条目中的一些其他泄漏),以及您可能想要仔细阅读的法律的非维基化版本。

于 2013-12-07T23:26:40.387 回答