迪米特法则 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.
高内聚低耦合
我们设计的目标就是高内聚低耦合,是否高内聚关键在于封装性
看段代码:
String name = book.getAuthor().getName();
可以推断出这行代码是获得一部作品作者的名字。
正常都能写出这段代码,因为在同一模块下大多数代码都是同一个人写的,所以Book和Author两个类都清楚里面的细节。但如果是不同人写的呢?至少得问下别人,作者名字在哪个类?或者翻阅类中实现细节。
从接口设计角度,外部只能知道Book对象,而Book中关联的对象细节是不需要知道的,这正是封装性的体现,降低认知负载。
而这也正好违背了LOD原则。
不该有直接依赖关系的类之间,不要有依赖;
有依赖关系的类之间,尽量只依赖必要的接口。
迪米特法则是希望减少类之间的耦合,让类越独立越好。LOD功效正是指导我们能设计出高内聚、
所以上面的代码得改动一下Book类
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
上面提到的代码
String name = book.getAuthor().getName();
开始没有意识到违背LOD,但至少会有null情况,所以会改成
if(book != null) { Author author = book.getAuthor(); if(author != null) { name = author.getName(); } }
如果有好几层,就会嵌套很多层。
但从JDK8之后,有了Optional,代码就变成了
name = Optional.ofNullable(book).map(Book::getAuthor).map(Author::getName);
虽然这样子很简洁地解决了null的问题,但是不是一样违背LOD呢?
延伸一下,是不是链式调用都违反了LOD,毕竟形式上的确很像,一个连接符接着一个连接符。
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[1]
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.[2]
所以结论是什么呢?
我想重要的还是共识,团队内的共识。就像在JDK7~JDK8的过渡期,团队中有先行者引入了stream新特性,很多成员看得头大,制定了团队公约,不要使用stream。但现在fluent interface已经很亲民了,自然团队对stream有更高的认同,有了共识,谁还会抵制它。
RESTful API
再扩展一下,看到很长的RESTful风格的url,这是不是也有违背LOD原则的嫌疑?
在RESTful API场景下,实体只有客户端和API提供者,API内部的实现细节也被API层屏蔽了,所以并不会违背LOD原则。
总结
LOD原则指导我们更好地封装,达到内聚性目标。但通过fluent interface,也认识到任何原则都是指导性,不可教条。考虑原则的同时,还得考虑ROI,而且还得与其它原则权衡,达到最适合的设计。
References
[1]
Stream shows that you are using it as intended by the Java language designers: https://stackoverflow.com/questions/45491555/can-multiple-operations-with-streaming-break-the-law-of-demeter
[2]
All these laws/principles are rarely absolute and more guidelines than dogmas. In this case it might be about balancing LoD vs. KISS.: https://stackoverflow.com/questions/47347068/optional-monad-and-the-law-of-demeter-in-java
[3]
The Genius of the Law of Demeter: https://dzone.com/articles/the-genius-of-the-law-of-demeter
[4]
LOD原则的高明之处: https://www.zybuluo.com/XingdingCAO/note/913912