关于不可知的语言:流畅的界面是否违反Demeter定律?

关于不可知的语言:流畅的界面是否违反Demeter定律?

Do fluent interfaces violate the Law of Demeter?

维基百科上有关Demeter定律的文章说:

The law can be stated simply as"use only one dot".

但是,流利接口的一个简单示例如下所示:

1
2
3
4
5
6
7
8
static void Main(string[] args)
{
   new ZRLabs.Yael.Pipeline("cat.webp")
        .Rotate(90)
        .Watermark("Monkey")
        .RoundCorners(100, Color.Bisque)
        .Save("test.webp");
}

那么,这在一起吗?


好吧,法律的简短定义将其缩短得太多。真正的"法律"(实际上是关于良好API设计的建议)基本上说:仅访问您自己创建的对象或作为参数传递给您的对象。不要通过其他对象间接访问对象。流利的接口方法通常会返回对象本身,因此,如果再次使用该对象,它们不会违反法律。其他方法可以为您创建对象,因此也不会违反。

另请注意,"法律"只是"经典" API的最佳做法建议。流利的接口是API设计的完全不同的方法,无法用Demeter定律进行评估。


不必要。"仅使用一个点"是对得墨meter耳定律的不准确总结。

当每个点代表不同对象的结果时,德米特耳定律不鼓励使用多个点,例如:

  • 第一个点是从ObjectA调用的方法,该方法返回ObjectB类型的对象
  • 下一个点是仅在ObjectB中可用的方法,它返回ObjectC类型的对象
  • 下一个点是仅在ObjectC中可用的属性
  • 广告无限

但是,至少在我看来,如果每个点的返回对象仍与原始调用者具有相同的类型,则不会违反Demeter法则:

1
2
3
var List<SomeObj> list = new List<SomeObj>();
//initialize data here
return list.FindAll( i => i == someValue ).Sort( i1, i2 => i2 > i1).ToArray();

在上面的示例中,FindAll()和Sort()都返回与原始列表相同类型的对象。没有违反《得墨meter耳法则》:该清单仅与其直系朋友交谈。

话虽如此,但并非所有流畅的接口都违反Demeter法则,只要它们返回与调用方相同的类型即可。


是的,尽管您必须对这种情况采取一些务实的态度。我总是以《得墨meter耳法则》为准则,而不是规则。

当然,您可能希望避免以下情况:

1
CurrentCustomer.Orders[0].Manufacturer.Address.Email(text);

也许替换为:

1
CurrentCustomer.Orders[0].EmailManufacturer(text);

随着越来越多的人使用ORM(通常将整个域表示为一个对象图),可能会为特定对象定义可接受的"范围"。也许我们应该采用demeter定律来建议您不要将整个图形映射为可到达。


得墨meter耳定律的精神是,在给定对象引用或类的情况下,应避免访问距离一个子属性或方法不多的类的属性,因为这会紧密耦合这两个类,这可能是意料之外的,并且可能导致可维护性问题。

流利的接口是法律可接受的例外,因为它们的含义至少是紧密耦合的,因为所有的属性和方法都是组合在一起构成功能语句的迷你语言的术语。


1)完全没有违反它。

该代码等效于

1
2
3
4
5
var a = new ZRLabs.Yael.Pipeline("cat.webp");
a = a.Rotate(90);
a = a.Watermark("Monkey");
a = a.RoundCorners(100, Color.Bisque);
a = a.Save("test.webp");

2)就像奥尔·菲尔·哈克(Ol'Phil Haack)所说:得墨meter耳定律不是计算点数的练习


从本质上讲,对象不应公开其内部(数据),而应公开用于内部操作的函数。

考虑到这一点,Fluent API要求对象使用其数据处理某些事情,而不是查询其数据。

这并不违反任何《得墨meter耳定律》。


您的示例没有问题。毕竟,您正在旋转,加水印等……始终是同一张图片。我相信您一直在与Pipeline对象进行对话,因此,只要您的代码仅取决于Pipeline的类,就不会违反LoD。


推荐阅读