根据得墨'耳(Demeter)的法律,是否允许某类返回其成员之一?


13

关于德米特定律,我有三个问题。

除了专门指定用于返回对象的类(例如工厂类和构建器类)之外,还可以使用一种方法来返回对象,例如,由该类的一个属性持有的对象,或者会违反demeter(1)的定律。?并且,如果它违反了demeter的定律,那么返回的对象是否是一个不可变的对象(表示一个数据,并且除了该数据的吸气剂之外就什么也不包含)(2a),这会很重要吗?还是这样的ValueObject本身就是反模式,因为使用该类中的数据完成的所有操作都是在类(2b)之外完成的?

用伪代码:

class A {}

class B {

    private A a;

    public A getA() {
        return this.a;
    }
}

class C {

    private B b;
    private X x;

    public void main() {
        // Is it okay for B.getA to exist?
        A a = this.b.getA();

        a.doSomething();

        x.doSomethingElse(a);
    }
}

我怀疑得墨meter耳的定律禁止这种模式。我该怎么做才能确保doSomethingElse()在不违反法律的情况下可以被调用(3)?


9
许多人(尤其是函数程序员)将Demeter定律视为反模式。其他人则将其视为“ Demeter偶尔有用的建议”。
David Hammen

1
我要赞成这个问题,因为它说明了得墨Law耳法则的荒谬之处。是的,LoD有时“有用”,但通常很愚蠢。
user949300 '16

如何x设置?
candied_orange

@CandiedOrange x只是一个不同的属性,其值只是一些可以调用的对象doSomethingElse
user2180613

1
您的示例没有违反LOD。LOD是关于防止对象的客户端耦合到该对象的内部细节或协作者。您想避免类似的事情user.currentSession.isLoggedIn()。因为它user不仅将的客户与user合作者耦合在一起session。相反,您希望能够书写user.isLoggedIn()。这通常可以通过isLoggedIn向其user实现委托的方法添加一个方法来实现currentSession
约拿(Jonah)2016年

Answers:


7

在许多编程语言中,所有返回的值都是对象。正如其他人所说,无法使用返回对象的方法会迫使您根本不返回任何东西。您应该问自己:“ A,B和C类的职责是什么?” 这就是为什么使用诸如A,B和C之类的元语法变量名总是要求答案“取决于”的原因,因为在这些术语中没有继承责任。

您可能需要研究域驱动设计,这将为您提供一组更加细致的启发式方法,以推断出功能应该去哪里以及谁应该调用什么。

关于不可变对象的第二个问题是与实体对象相比,
值对象的概念。在DDD中,几乎可以无限制地传递值对象。对于实体对象,这不是正确的。

简单的LOD可以更好地用DDD表示为访问聚合的规则。任何外部对象都不应包含对聚合成员的引用。仅应保留一个根引用。

除了DDD,您至少想为自己的类开发适合您的域的构造型集。在设计系统时,强制执行有关这些构造型的规则。

还要始终记住,所有这些规则都是为了管理复杂性,而不是束缚自己。


1
DDD是否提供有关何时可以暴露类成员(如数据)(即应该存在吸气剂)的规则?所有的讨论围绕Law of Demetertell, don't askgetters are evilobject encapsulationsingle responsibility principle似乎归结为这样一个问题:何时应该委派一个域对象使用,而不是获取对象的值,做它的东西吗?能够将微妙的资格应用于必须做出此类决定的情况将非常有用。
user2180613

之所以存在诸如“获取器是邪恶的”之类的准则,是因为许多人将类实例视为数据结构(要传递的一袋数据)。这不是面向对象的编程。对象是实际上提供一些工作的功能/行为束。工作范围由职责定义。显然,这项工作将创建从方法等返回的对象。如果您的类实际上正在做有意义的工作,而您可以清楚地定义“用属性Y和Z表示数据对象X”,那么您就走对了。 。我不会对此感到压力。
杰里米(Jeremy)

为了进一步说明这一点,当您使用大量的吸气剂/吸气剂时,这通常意味着您在某个地方需要一些大型方法来编排所有内容,Fowler称之为事务脚本。在开始使用吸气剂之前,请先问问自己……我为什么要从对象中请求此数据?为什么在对象的方法中没有此功能?如果实现此功能不是此类的职责,请继续使用吸气剂。福勒(Fowler)的[重构](martinfowler.com/books/refactoring.html)是一本关于启发式问题的书。
杰里米(Jeremy)

沃恩·弗农(Vaughn Vernon)在他的《实现域驱动的设计》一书中提到了《德米特与德耳,定律,不要问》,第10章,第382页。如果可以使用它,值得一看。
杰里米(Jeremy)

6

根据得墨'耳(Demeter)的法律,是否允许某类返回其成员之一?

是的,最肯定的是。

让我们看一下要点

  • 每个单元对其他单元的了解应该有限:只有与当前单元“紧密”相关的单元。
  • 每个单位只能与朋友交谈;不要跟陌生人说话。
  • 只与您的直系朋友交谈。

所有这三个问题都让您问一个问题: 谁是朋友?

在决定要返回什么时,《得墨meter耳定律》或最低知识原则(LoD)并不表示您要捍卫反对坚持违反该原则的编码人员。它规定您不要强迫编码人员违反它。

正是这些使很多人感到困惑的原因,就是为什么这么多人认为二传手必须总是返回void。不能。您必须允许一种进行查询(获取)的方式,而该方式不会更改系统状态。这是基本的命令查询分离

这是否意味着您可以随意钻研链接所需的代码库?否。仅将要链接的内容链接在一起。否则,链条可能会改变,突然间您的东西坏了。这就是朋友的意思。

可以设计长链。流利的接口,iDSL,许多Java8和良好的旧StringBuilder都是为了让您构建长链。他们不会违反LoD,因为链中的所有内容都旨在一起工作,并承诺继续合作。当您将从未听说过的东西链接在一起时,就会违反demeter。朋友是那些承诺让您的连锁店保持运转的人。朋友之友没有。

除了专门指定用于返回对象的类(例如工厂类和构建器类)之外,还可以使用一种方法来返回对象,例如,由该类的一个属性持有的对象,或者会违反demeter(1)的定律。 ?

这只会创造违反demeter的机会。这不是违反。这甚至不一定是坏的。

并且,如果它违反了demeter的定律,那么返回的对象是否是代表一个数据并且不包含除该数据的吸气剂之外的任何东西的不可变对象,是否会很重要(2)?

不变是好的,但在这里无关紧要。通过更长的链来获取知识并不能使事情变得更好。更好的是将使用与使用分开。如果您正在使用,请询问您需要什么作为参数。不要去研究陌生人的吸血鬼。

用伪代码:

我怀疑得墨meter耳的定律禁止这种模式。我该怎么做才能确保在不违反法律的情况下可以调用doSomethingElse()(3)?

在我开始谈论之前x.doSomethingElse(a),先要了解一下您已经写了

b.getA().doSomething()

现在,LoD并不是点计数练习。但是,当您创建链时,您的意思是您知道如何A(使用B)获得和使用A。现在好了AB最好是亲密的朋友,因为您只是将他们联系在一起。

如果你刚刚问的东西交给你的A喜欢的东西,你可以使用A并不会在意它是从哪里来的,并B过上幸福生活和自由的痴迷与得到AB

至于x.doSomethingElse(a)没有具体来源的细节x,LoD对此无话可说。

LoD可以鼓励将使用与建筑分开。但是我要指出的是,如果您虔诚地将每个对象视为不友好的对象,那么您将被困在静态方法中编写代码。您可以通过这种方式主要构建一个非常复杂的对象图,但是最终您必须在其上调用一个方法以使事物开始工作。您只需确定谁是您的朋友。没有摆脱它的地方。

因此,是的,允许类在LoD下返回其成员之一。在执行此操作时,您应该弄清楚该成员是否与您的班级友好,因为如果不是,则某些客户端可能会尝试在使用它之前通过使用您将其耦合到该班级。这很重要,因为现在您返回的内容必须始终支持该用法。

在很多情况下,这根本就不是问题。集合可以忽略这一点,只是因为它们旨在成为使用它们的一切的朋友。同样,价值对象对每个人都友好。但是,如果您编写了一个地址验证实用程序,该实用程序要求从中提取一个员工对象的员工对象,而不是仅索要该地址,那么您最好希望该员工和地址都来自同一个库。


由于一些友好的概念,流利的接口也不是LOD的例外....在流利的接口中,链中的每个方法都在同一对象上调用,因为它们都返回自身。 settings.darkTheme().monospaceFont()只是语法糖settings.darkTheme(); settings.monospaceFont();
约拿(Jonah)2016年

3
@Jonah回归自我是终极的友善。并非所有流畅的界面都可以。许多iDSL也很流利,并且返回不同的类型以在不同点更改可用的方法。考虑一下jOOQ步骤构建器向导构建器和我自己的噩梦怪兽构建器。流利是一种风格。不是模式。
candied_orange

2

除了专门指定用于返回对象的类之外,[...]

我不太明白这个说法。消息调用可以使任何对象返回一个对象。特别是如果您认为“一切都作为对象”。

但是,类是唯一可以通过向其发送“新”消息(或在大多数常见的OOP语言中经常用new关键字替换)实例化其自身的新对象的对象。


无论如何,关于您的问题,我将对一个对象返回其属性之一以便在其他地方使用感到非常怀疑。该对象可能会泄漏有关其状态或实现细节的信息。

而且您不希望C对象了解B的实现细节。从C对象的角度来看,这是对A对象的有害依赖。

因此,您可能想问自己,C是否通过了解A对自己要做的工作不了解太多,而与LOD无关。

在某些情况下,这可能是合法的,例如,向收集对象索要其一项是适当的,而不违反任何Demeter规则。另一方面,向Book对象请求其SQLConnection对象是有风险的,应该避免。

之所以存在LOD,是因为许多程序员倾向于在对对象进行信息处理之前向对象询问信息。LOD在那里提醒我们,有时候(如果不是总是这样),我们只是通过让我们的对象做太多事情而使事情变得过于复杂。有时我们只需要问另一个对象为我们完成工作即可。LOD使我们三思而后行将行为放置在对象中的位置。


0

除了专门指定用于返回对象的类(例如工厂类和构建器类)之外,方法可以返回对象吗?

为什么不呢?您似乎建议有必要向您的其他程序员发出信号,该类专门用于返回对象,或者它根本不能返回对象。在一切都是对象的语言中,这将严重限制您的选择,因为您根本无法返回任何内容。


这个例子是关于隐式的b.getA().doSomething()x.doSomethingElse(b.getA())
user2180613

A a = b.getA(); a.DoSomething();-回到一个点。
罗伯特·哈维

2
@ user2180613-如果您想询问b.getA().doSomething(),则x.doSomethingElse(b.getA())应该明确询问。就目前而言,您的问题似乎是关于this.b.getA()
David Hammen

1
由于A不是Cin 的成员,未在in中创建C和未提供给in C,因此Ain C(以任何方式使用)似乎违反了LoD。点计数不是LoD的目的,它只是一个说明性工具。可以将其转换Driver().getCar().getSteeringWheel().getFrontWheels().toRight()成单独的语句,每个语句包含一个点,但是仍然存在违反LoD的情况。我在该论坛上的许多帖子中都读到,应该将执行操作委派给实现实际行为的对象,即tell don't ask。返回值似乎与此冲突。
user2180613

1
我想这是真的,但它不能回答问题。
user2180613

0

取决于您在做什么。如果公开您的班级成员,那么当成为班级成员或该成员的类型与谁无关时,那是一件坏事。如果您随后更改该成员的类型以及返回该成员的函数的类型,并且每个人都必须更改其代码,那是邪恶的。

另一方面,如果您的类的接口声明它将提供一个著名的类A的实例,那很好。如果您幸运的话,可以通过提供您班上的一名成员来做到这一点,对您有好处。如果将成员从A更改为B,则需要重写返回A的方法,显然现在它必须构造一个并用可用数据填充它,而不仅仅是返回一个直线成员。您的类的接口将保持不变。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.