什么是得墨忒耳法则?

What is Law of Demeter?

让我们从维基百科开始:

More formally, the Law of Demeter for functions requires that a method m of an object O may only invoke the methods of the following kinds of objects:

  1. O itself
  2. m's parameters
  3. Any objects created/instantiated within m
  4. O's direct component objects
  5. A global variable, accessible by O, in the scope of m

规则 1:

public class ClassOne {

    public void method1() {
        method2();
    }

    public void method2() {

    }
}

规则 2:

public class ClassOne {

    public void method1(ClassTwo classTwo) {
        classTwo.method2();
    }
}

class ClassTwo {

    public void method2() {

    }
}

规则 3:

public class ClassOne {

    public void method1() {
        ClassTwo classTwo = new ClassTwo();
        classTwo.method2();
    }
}

class ClassTwo {

    public void method2() {

    }
}

规则 4(感谢@juharr):

public class ClassOne {

    private ClassTwo classTwo;

    public void method1() {
        classTwo = new ClassTwo();
        classTwo.method2();
    }
}

class ClassTwo {

    public void method2() {

    }
}

规则 5:

?

谁能帮我解决规则 5?


Demeter 法则不是暗示链接不好吗?

User.getName().getLastName();

这会导致高耦合。


"Tell, don't ask"不也是类似的道理吗?

这就是全部了吗?我错了什么吗?你怎么能遵守得墨忒耳法则呢?

"Tell don't ask"有点不同。

得墨忒尔:不要为了得到某事而从中得到某事,最后做某事。

TDA:不要从另一个对象中检索 "information" 然后再对此做出决定。简单例子:

if (someList.size() == 0) { bla

对比

if (someList.isEmpty()) { bla

在这两种情况下,您都在调用其他对象的方法;但有一个关键区别:第一个调用向您公开了另一个对象的 "internal" 状态;然后你做出一些决定。鉴于,在 "TDA" 改进的第二个版本中;您将 "status evaluation" 留在另一个对象中;从而以某种方式减少耦合。

但仅作记录:第二个示例仍然根据该列表的状态做出决定。从这个角度来看,它只是比选项 1 稍微 更好的版本。理想情况下,您不需要此类检查。

第 5 个很难用 C# 或 Java 表示,因为它们在技术上不支持全局变量。但是,在原则上相似的设计模式中,您可以拥有例如仅包含全局可访问静态配置值的配置 class,例如 (C#):

internal class MyConfiguration
{
    private static String MyConfigurationValue; // set in constructor
    MyConfiguration(){ MyConfigurationValue = DoSomethingToLoadValue(); }
    public static String GetMyConfigurationValue(){ return MyConfigurationValue; }
}

在这种情况下(假设设计模式在所有其他方面都是可接受的),Demeter 法则将允许这样做,因为它是全球可访问的,并且打算这样做。

规则 5 的示例是:

public class ClassOne {
    public void method1() {
        classTwo.STATIC_INSTANCE.method2();
    }
}

class ClassTwo {
    public static final ClassTwo STATIC_INSTANCE = ...;

    public void method2() {
    }
}

枚举基本上是这样工作的,访问枚举是可以的。


你的例子:

user.getName().getLastName();

显然违反了法律,因为您从 "getName()" 获得的物品不属于所列的任何类别。注意:即使你没有使用链调用也是错误的:

Name name = user.getName();
name.getLastName(); // <- this is still wrong

因为对象 "name" 仍然不属于任何列出的类别。

不过这样也可以:

reportBuilder.withMargin(5).withFooter(10)
    .withBorder(Color.black).build();

为什么允许这样做?因为您要么每次都返回相同的对象(r​​eportBuilder),要么每次都返回一个新对象(如果构建器实现为不可变的)。无论哪种方式,它都属于法则 2 或 3,所以无论哪种方式都可以。


你的第三个问题是"how to obey"。好吧,这是一个复杂的问题,但首先,想想法律实际上禁止什么样的方法!

只需将定律变为否定:我们不应该在已经存在的对象上调用方法(因为新对象是豁免的),并且不是我的对象、我的对象的字段或我的参数。这样就留下了 other objects!

字段中的对象

所以基本上这意味着您不应该 "get" 访问不属于您、不在您的字段中以及不直接参数的对象。我总结为 "no getters"!