SOLID之LOD

迪米特法则 Law of Demeter

LOD

最早出现于 1987年,由美国东北大学的伊恩·霍兰德(Ian Holland)提出。也叫做“最少知识原则”。

Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers.

高内聚低耦合

我们设计的目标就是高内聚低耦合,是否高内聚关键在于封装性

看段代码:

1
String name = book.getAuthor().getName();

可以推断出这行代码是获得一部作品作者的名字。

正常都能写出这段代码,因为在同一模块下大多数代码都是同一个人写的,所以Book和Author两个类都清楚里面的细节。但如果是不同人写的呢?至少得问下别人,作者名字在哪个类?或者翻阅类中实现细节。

从接口设计角度,外部只能知道Book对象,而Book中关联的对象细节是不需要知道的,这正是封装性的体现,降低认知负载。

而这也正好违背了LOD原则。

不该有直接依赖关系的类之间,不要有依赖;

有依赖关系的类之间,尽量只依赖必要的接口。

迪米特法则是希望减少类之间的耦合,让类越独立越好。LOD功效正是指导我们能设计出高内聚、

所以上面的代码得改动一下Book类

1
2
3
4
5
class Book {
public String getAuthorName(){
return author.getName();
}
}

谁是朋友?

既然LOD指出只跟自己的朋友交流,那得搞清楚谁是朋友?

对于一个类C,它拥有的一个方法称为M。
方法M发送信息的目标对象必须为:

  • M方法的参数对象,还有对自身的引用(被方法M创建的实例对象、被方法M调用的方法生成的实例对象、全局对象)。
  • 类C的实例对象。

把上面的情况拆解开:

  1. 本身(this,self)
  2. 方法M的参数对象(parameter)
  3. 类C内实例变量引用的对象(instance variable)
  4. 被方法M创建的对象 或 方法M调用的方法创建的对象
  5. 如果实例变量是集合,集合中的对象(collection,aggregration)

示例

在正统的SOLID原则中,都不包含LOD原则,但它确实能很好地指导我们如何封装,达到高内聚的目标。

下面看两个常见到的问题作为示例,进一步理解这个原则。

流式API

上面提到的代码

1
String name = book.getAuthor().getName();

开始没有意识到违背LOD,但至少会有null情况,所以会改成

1
2
3
4
5
6
if(book != null) {
Author author = book.getAuthor();
if(author != null) {
name = author.getName();
}
}

如果有好几层,就会嵌套很多层。

但从JDK8之后,有了Optional,代码就变成了

1
name = Optional.ofNullable(book).map(Book::getAuthor).map(Author::getName);

虽然这样子很简洁地解决了null的问题,但是不是一样违背LOD呢?

延伸一下,是不是链式调用都违反了LOD,毕竟形式上的确很像,一个连接符接着一个连接符。

1
2
3
4
5
6
Report report = new ReportBuilder()
.withBorder(1)
.withBorderColor(Color.black)
.withMargin(3)
.withTitle("Law of Demeter Report")
.build();

如我们常见的builder方式,每次返回的都是self。根据“谁是朋友”的定义是允许的。这是简单的示例,但像上面的Optional呢?

每次Optional.map之后,其实并不是最初的Optional对象了,Stream也一样,每次返回的都是一个新的Stream对象。

怎么解释这种问题呢?这个问题其实有很多人提出疑问,大概有这么几种回答:

1、Stream shows that you are using it as intended by the Java language designers

2、any standard library objects should be exempt from the Law of Demeter

3、All these laws/principles are rarely absolute and more guidelines than dogmas. In this case it might be about balancing LoD vs. KISS.

所以结论是什么呢?

我想重要的还是共识,团队内的共识。就像在JDK7~JDK8的过渡期,团队中有先行者引入了stream新特性,很多成员看得头大,制定了团队公约,不要使用stream。但现在fluent interface已经很亲民了,自然团队对stream有更高的认同,有了共识,谁还会抵制它。

RESTful API

再扩展一下,看到很长的RESTful风格的url,这是不是也有违背LOD原则的嫌疑?

在RESTful API场景下,实体只有客户端和API提供者,API内部的实现细节也被API层屏蔽了,所以并不会违背LOD原则。

总结

LOD原则指导我们更好地封装,达到内聚性目标。但通过fluent interface,也认识到任何原则都是指导性,不可教条。考虑原则的同时,还得考虑ROI,而且还得与其它原则权衡,达到最适合的设计。

参考资料

The Genius of the Law of Demeter

LOD原则的高明之处

公众号:码农戏码
欢迎关注微信公众号『码农戏码』