迪米特法则同样是来帮助开发者写出“高内聚、松耦合”的程序
迪米特法则的含义是这样的
“不该有依赖关系的模块之间,不要有依赖;有依赖关系的模块之间,尽量只依赖必要接口”
高内聚、低耦合是一个比较通用的思想,可以应用到不同粒度的设计当中,比如一个模块,一个类、一个函数中
下午通过两个例子来介绍迪米特法则
实例一
我们通过该实例来看一下迪米特法则的前半段--“不该有依赖关系的模块之间,不要有依赖”
有三个类,NetworkTransporter负责底层网络请求,HtmlDownloader通过URL请求网页html数据,Document表示网页文档数据对象,后续网页内容的抽取、分析都依赖这个类
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);
}
//...
}
- 首先
NetworkTransporter类,它用来发送网络请求,不应该依赖HTMLRequest类,依赖它就好像只能发送HTML的请求了。就好像你去商店里买东西付钱的时候,如果这样的写法就好像你直接把钱包给了店主,让店主自己拿钱。正确的做法应该是你拿出钱给店主 NetworkTransporter没必要依赖其他request类,只接受必需的参数即可
public class NetworkTransporter {
// 省略属性和其他方法...
public Byte[] send(String url, Byte[] data) {
//...
}
}
HtmlDownloader类设计的没啥问题,需要下载数据所以必须依赖NetworkTransporter,但因为网络请求类修改了,它也得做相应修改
public class HtmlDownloader {
private NetworkTransporter transporter;//通过构造函数或IOC注入
// HtmlDownloader这里也要有相应的修改
public Html downloadHtml(String url) {
HtmlRequest htmlRequest = new HtmlRequest(url);
Byte[] rawHtml = transporter.send(
htmlRequest.getAddress(), htmlRequest.getContent().getBytes());
return new Html(rawHtml);
}
}
再来看一下Document类,有如下几个问题:
- 在构造函数中执行下载操作不合适,时机不对,耗时较长,不易测试
HtmlDownloader通过new的方式初始化,违反了基于接口而非实现编程的设计思想- 从业务含义上来说
Document不应该依赖HtmlDownloader
基于上面的问题,我们做如下修改
Document类中只有url和html数据- 通过一个工厂类来创建
Document对象 - 通过依赖注入方式给工厂类传递
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;
}
//...
}
这样便能满足要求了
但你可能会说,不就序列化和反序列化两个方法么,至于设计的这么复杂么?是不是过渡设计了?
设计原则本身没有错,只有说能否用对之说。不要为了应用设计原则而强行用设计原则,我们在应用设计原则的时候,要具体问题具体分析
如果仅有序列化和反序列化两个方法,其实放到一个类里面就行了,对于只需要序列化的场景的类,其实能调用到反序列化方法问题也不大,带来的影响并不大
但如果随着项目发展,序列化和反序列化越来越多,比如
public class Serialization { // 参看JSON的接口定义
public String serialize(Object object) { //... }
public String serializeMap(Map map) { //... }
public String serializeList(List list) { //... }
public Object deserialize(String objectString) { //... }
public Map deserializeMap(String mapString) { //... }
public List deserializeList(String listString) { //... }
}
当序列化和反序列化的方法都增加到了3个,那对于只使用序列化的使用者来说,一旦反序列化的实现发生了变化,我们可能就得检查依赖Serialization的地方是否正常工作