迪米特原则,是一个很常见的原则,不像是SOLID KISS DRY那样人尽皆知,但是可以高效的提高代码的可读性和可维护性
那么,什么是迪米特原则,就是今天的话题
1.什么是高内聚,低耦合
一个重要的设计思想,能够有效的提高代码的可维护性和可读性,缩小功能改动导致的代码改动范围
很多的设计原则都是为了代码的高内聚 松耦合去实现的,比如单一职责原则,基于接口而非实现编程等
实际上,高内聚,松耦合是一种通用的设计思想,知道不同粒度的代码的设计开发
那么,什么是高内聚呢?
就是,功能相近,就应该放到一个类中,不相近的功能不要放到一个类中,修改起来集中,提高了可维护性,之前说的单一职责原则就是实现高内聚有效设计原则
那么,什么是松耦合呢?
就是类和类之间的关系,要清晰简单,避免过低的交互,在修改一个类的时候,尽量避免另一个类的修改,也就是依赖反转,接口隔离原则,基于接口而非实现编程,里氏替换原则等,都是去依赖于抽象的规范或者接口,从而提高代码的松耦合特性
高内聚,低耦合的具体实现,可以如下图
如果足够高内聚,低耦合,那么就会想左边图一样,一个类的修改,不会影响到其他类
但是耦合高了,就会像右图一样,一个类的修改,影响到了其他类,牵一发而动全身
2.迪米特法则的详解
英文翻译为 Law of Demter,名字很抽象,于是其还有一个额外信达雅的名字,叫最小知识原则
含义为:每个模块Unit,只了解那些和它关系密切的模块的有限知识
或者说,不该有依赖关系的类之间,不能有依赖关系,有依赖关系之间的类,尽量只依赖必要的接口
这个可以分为两个部分理解
没有必要依赖的类之间,不要有依赖
关于这一点,我们拿一个代码实现来说明
public class NetworkTransporter {
// 省略属性和其他方法… public Byte[] send(HtmlRequest htmlRequest) { //… } } public class HtmlDownloader { private NetworkTransporter transporter;//通过构造函数或IOC注入 public Html downloadHtml(String url) { Byte[] rawHtml = transporter.send(new HtmlRequest(url)); return new Html(rawHtml); } } public class Document { private Html html; private String url; public Document(String url) { this.url = url; HtmlDownloader downloader = new HtmlDownloader(); this.html = downloader.downloadHtml(url); } //… } |
上面有三个类,三个类彼此有所依赖,首先看NetwordTransporter类,是一个发送信息的类,但是传入的参数是一个htmlRequest,对于这种可以复用的类,我们希望其不要依赖与具体的对象,将其变得更加通用性,我们可以将HtmlRequest对象,换为其组成部分,也就是其中的address和content对象,只传递这两个对象,可以提高复用性,符合迪米特原则
修改后的代码如下
public class NetworkTransporter {
// 省略属性和其他方法…
public Byte[] send(String address, Byte[] data) {
//…
}
}
再来看HtmlDownloader类
这个类,符合迪米特原则,只需要按照NetworkTransporter的修改进行修改即可
再来看最后一个Document类,这个类的构造函数中,我们进行了生成HtmlDownloader对象,不符合基于接口而非实现原则,可以考虑使用依赖注入
其次downloadHtml太过于耗时,毕竟是下载一个html,不应该放到构造函数中,而且也没有必要去依赖HtmlDownloader类,可以考虑加入一个工厂类,利用工厂类来进行下载,虽然没有提高总体时间,但是避免了其在初始化函数中过慢的问题
public class Document {
private Html html; private String url; public Document(String url, Html html) { this.html = html; this.url = url; } //… } // 通过一个工厂方法来创建Document public class DocumentFactory { private HtmlDownloader downloader; public DocumentFactory(HtmlDownloader downloader) { this.downloader = downloader; } public Document createDocument(String url) { Html html = downloader.downloadHtml(url); return new Document(url, html); } } |
有依赖关系之间的类,尽量只依赖必要的接口
如何理解这个后半段呢?很简单,利用一个之前的类来进行描述
Serialization来进行负责对象的序列化和反序列化
public class Serialization {
public String serialize(Object object) { String serializedResult = …; //… return serializedResult; } public Object deserialize(String str) { Object deserializedResult = …; //… return deserializedResult; } } |
这个类的使用来说,有一种情况,就是有些类只会进行序列化,而有的类只会进行反序列化
那么,如果按照迪米特原则来说,我们应该讲Serialization类拆为两个粒度更小的类,一个负责序列化,一个负责反序列化
public class Serializer {
public String serialize(Object object) { String serializedResult = …; … return serializedResult; } } public class Deserializer { public Object deserialize(String str) { Object deserializedResult = …; … return deserializedResult; } } |
那么按照单一职责原则来说,这是不符合的,做的有点过了,导致聚合性降低了
那么,如何去做一个既符合迪米特法则,有不破坏内聚原则的实现呢?
那么就可以按照接口案例原则,进行拆分,拆分为两个接口
public interface Serializable {
String serialize(Object object); } public interface Deserializable { Object deserialize(String text); } public class Serialization implements Serializable, Deserializable { @Override public String serialize(Object object) { String serializedResult = …; … return serializedResult; } @Override public Object deserialize(String str) { Object deserializedResult = …; … return deserializedResult; } } public class DemoClass_1 { private Serializable serializer; public Demo(Serializable serializer) { this.serializer = serializer; } //… } public class DemoClass_2 { private Deserializable deserializer; public Demo(Deserializable deserializer) { this.deserializer = deserializer; } //… } |
那么,这就既体现了迪米特法则,又没有降低内聚性
对于这个实现的思考延伸
如果只是上面的一个类中包含两个操作,有必要拆为两个接口吗,是否有点过度设计了呢?
只是为应用设计原则而应用设计原则,是否有点过于了呢?
但是,如果之后可能出现,为序列化,反序列化提供更多的需求,那么拆分就有必要了
最后,本章重点:
1.如何理解高内聚,低耦合
这是一种指导思想,能够有效的提高代码可读性和可维护性
高内聚用于设计一个类,低耦合用于阐述类和类之间的依赖关系
功能相近的类放到一个类中,不相近的功能不要放到一个类中,
而且,类和类之间的依赖关系简单清晰,做到类的改动不会导致其他类的改动
2.如何理解迪米特法则
不该有依赖关系的类,不要有依赖,有依赖关系的类,只依赖必要的接口
让类之间更加独立,每个类都少接触其他部分,减少类之间的改动