24

每当我看到有关得墨忒耳法则的文章时,作者似乎从来没有给出一个如何遵守这条法则的可靠例子。他们都解释了它是什么,并展示了一个违法的例子,但这很容易。

可能有很多方法可以遵守这条定律(好的设计和规划是其中之一),但简单来说,这是一种遵守它的方法吗?

假设我有一个具有这些属性的类:

public class Band {

    private Singer singer;
    private Drummer drummer;
    private Guitarist guitarist;
}

我在程序的某个地方,我有这个Band类的一个实例,我想要吉他手的名字,我通常看到的是这样的:

guitaristName = band.getGuitarist().getName();

那似乎并不算太​​糟糕,因为它并没有在链条中走得太深,但是得墨忒耳法则说也许应该这样做:

guitaristName = band.getGuitaristName();

我的Band班级有一个方法:

public String getGuitaristName() {
    return guitarist.getName();
}

这是你应该遵守法律的方式吗?

谢谢。

4

4 回答 4

45

正如Dancrumb上面所说,我认为法律的理念是确保人们在适当的级别访问对象。

想象一下,我们的Band班级模拟了乐队办公室的前台。它的工作是充当乐队成员的 PA 并与任何想与乐队互动的人打交道。

现在假设我们有一位来自公关公司的巡演发起人,他想为他们的下一次巡演整理一些公关材料。我们可以用一个类来模拟他:

class TourPromoter {

  public String makePosterText(Band band) {
    String guitaristsName =  band.getGuitarist().getName();
    String drummersName = band.getDrummer().getName();
    String singersName = band.getSinger().getName();
    StringBuilder posterText = new StringBuilder();

    posterText.append(band.getName()
    posterText.append(" featuring: ");
    posterText.append(guitaristsName);
    posterText.append(", ");
    posterText.append(singersName);
    posterText.append(", ")
    posterText.append(drummersName);
    posterText.append(", ")
    posterText.append("Tickets £50.");

    return posterText.toString();
  }

}

在现实生活中,这相当于旅游发起人打电话给办公室并说:

  • 旅游推广人:我可以和你的吉他手谈谈吗?

  • 接待员:好的,我给你找他。

  • 吉他手:你好,我是吉他手

  • Tour Promter:嗨,我正在整理你的最新海报。只是想检查你的名字?

  • 吉他手:这是吉米佩奇

  • 旅游推广人:太好了,谢谢。哦,你能把你的鼓手给我吗?…</p>

可以说 PA 很快就会被解雇。大多数通情达理的人会问“你不能为我们处理那个电话吗?”

我们可以有一种getGuitaristsName()方法,从技术上讲,它会尊重 Demeter 法则,但我们仍然要求我们的TourPromoter班级记住关于乐队的细节——即他们有一个吉他手——而这些信息应该真正属于乐队本身。

为了确保我们以合理的方式引入方法,我们需要查看巡演发起人实际上在寻找什么——即所有乐队成员的姓名。如果我们在代码中对该方法进行建模,它为我们提供了更大的灵活性,可以在以后进行更改,Band甚至无需触摸TourPromoter

public class Band {
    private Singer singer;
    private Drummer drummer;
    private Guitarist guitarist;

    public String[] getMembers() {
        return {singer.getName(), drummer.getName(), guitarist.getName()};
    }  
}

public class TourPromoter {
    public String makePosterText(Band band) {
        StringBuilder posterText = new StringBuilder();

        posterText.append(band.getName());
        posterText.append(" featuring: "); 
        for(String member: band.getMembers()) {
            posterText.append(member);
            posterText.append(", ");
        }
        posterText.append("Tickets: £50");

        return posterText.toString();
    }    
}

如果我们现在添加一个 Bassist 或 KeyboardPlayer,则只有 Band 类需要知道区别,而 Tour Promoter 不需要更改。这意味着我们现在也尊重单一职责原则——即我们的makePosterText()方法只需要在我们改变海报格式时改变,而不是在乐队改变时。

我不认为得墨忒耳法则会告诉你需要采用哪种方法才能以最好的方式(例如getMembers(),而不是getGuitaristsName()上面)满足原则,我认为你是对的——它确实向你展示了当事情坏了,但不一定如何修复它们。不过,牢记 LoD 意味着您要留意违规行为,然后可以通过其他设计原则(如 SRP)的组合来修复这些违规行为。

于 2013-07-06T15:01:07.283 回答
29

您没有在适当的级别应用 LoD:两者都Band应该Guitarist被视为同一模块的一部分,并且您所面临的困境应以最大的方便为由来决定。

你的问题是一个更广泛问题的例子,我经常在关于设计模式和类似的书籍中遇到这个问题:他们试图解释涉及复杂系统设计的广泛原则,解决荒谬的小问题。结果只是读者的困惑。

实际上你会看到这个原理实际上是这样的:你正在使用AsyncHttpClient,这是一个构建在 之上的抽象Netty,它是一个构建在 Java NIO 之上的抽象。现在,如果AsyncHttpClient的 API 迫使您在某个地方直接管理 Java NIO 对象,其 API 更加原始,并且处理与 完全陌生的概念AsyncHttpClient,那将是破坏 LoD 的示例。

于 2013-06-30T18:07:54.603 回答
5

得墨忒耳定律本身并没有提供满足良好需求的方法论。充其量,它是识别潜在问题代码区域的有用启发式方法。违反法律可以被认为是“代码味道

它可能会扩大类的接口,因为它会产生代理或包装器方法,使您可以访问内部对象拥有的属性。因此,它的优点应该与它的缺点(潜在地创建笨重的类和模块)相平衡。

它最好用作识别可能需要重构的代码的工具。然而,一个简单的方法交换从

a.x.getFoo()

a.getXFoo()

遵循法律条文,而不遵循精神(在这一点上向@TedHopp致谢)。相反,您应该研究用户在刺穿a' 的抽象时试图做什么,以便获取x'foo并相应地定义该方法。这可能不是一项微不足道的任务,但这就是我们获得大笔报酬的原因:)

于 2013-06-30T18:11:20.650 回答
2

我会让一个乐队回答谁在演奏这个乐器的问题?

public String whoIsPlaying(String instrument);

然后,如果你需要一个吉他手的名字,你会说:

band.whoIsPlaying("guitar");

没有什么能阻止您对其他乐器进行 ping 操作。

于 2015-04-27T19:42:55.067 回答