迪米特原则 Law of Demeter, LOD
迪米特原则定义
迪米特原则也叫最小知识原则(The Least Knowledge Principle)。即,每个模块只应该了解那些与它关系密切的模块的有限知识,或者说,每个模块只和自己的朋友”说话“,不和陌生人”说话“。
这里的模块可以是微服务、框架、组件、类库、模块、类、甚至是函数,以下的描述主要以“类”作为迪米特原则的描述对象。
什么是朋友和陌生人
对类来说,出现在成员变量以及方法对输入输出参数中对类是朋友类,出现在方法体内部的类不是朋友类,是陌生人。
举例场景:Boss 给 TeamLeader 下指令,要求得到课程数量
public class Boss{
public void checkCourseNumber(Teamleader teamleader){
List<Course> courses = new ArrayList<>();
for(int i=0;i<20;i++){
courses.add(new Course());
}
teamleader.checkCourseNumber(courses);
}
}
class Teamleader{
public void checkCourseNumber(List<Course> courses){
System.out.println("课程数量:" + courses.size());
}
}
对于以上代码,Course并非Boss类的朋友类,也不需要知道Course的相关信息,但必须得引入Course类,使得Boss类不得不依赖Course类,提高了代码的耦合度。根据迪米特原则,应该把Course相关的移动到Teamleader类。
public class Boss{
public void checkCourseNumber(Teamleader teamleader){
teamleader.checkCourseNumber(courses);
}
}
class Teamleader{
public void checkCourseNumber(List<Course> courses){
List<Course> courses = new ArrayList<>();
for(int i=0;i<20;i++){
courses.add(new Course());
}
System.out.println("课程数量:" + courses.size());
}
}
迪米特原则的意义
迪米特原则对类而言,就是不该有直接依赖关系的类之间,不要使其有依赖关系;有依赖关系的类之间,尽量只依赖必要的接口(也就是定义中提到的有限知识)。尽量降低类之间的耦合,使代码“高内聚、松耦合“,提高代码的可读性和可维护性。
什么是高内聚、低耦合
“高内聚、低耦合”是一个通用的设计思想,单一职责原则、基于接口而非实现编程也都是以实现代码的“高内聚、低耦合”为目的。“高内聚”是用来指导类本身的设计,也就是相近的功能应该放在同一个类中,修改的时候会比较集中,代码容易维护。“松耦合”用来指导类与类之间依赖关系的设计,也就是类与类之间的依赖关系应该简单清晰。高内聚的实现有助于松耦合的实现。
举例场景:实现简易版的网页爬取。
NetworkTransporter: 负责底层网络通信,根据请求获取数据
HtmlDownloader: 负责通过URL获取网页
HtmlRequest:网页下载请求对象
Document:网页文档对象。
public class NetworkTransporter{
//省略其他方法和属性
public Byte[] send(HtmlRequest htmlRequest){
//...
}
}
public class HtmlDownloader{
private NetworkTransporter transporter;
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);
}
}
以上代码根据指定URL创建Document对象,就可以获取到爬取到的网页文档对象,虽然功能实现类,但是设计存在诸多缺陷,且违反类迪米特法则,代码能用但不好用。
1、NetworkTransporter作为底层网络通信类,功能应该尽可能通用,不应该因为依赖HtmlRequest类,而只服务于下载HTML,依赖类不该有直接依赖关系的上层HtmlRequest类。
2、Document类中将耗时长,逻辑复杂的downloader.downloadHtml()方法放到构造函数中,不利于代码的测试,且直接在构造函数中new HtmlDownloader对象,不仅违反了基于接口而非实现编程的原则,也违背类迪米特法则,因为从业务角度看,Document类作为基础对象类,没必要依赖HtmlDownloader类。
public class NetworkTransporter{
//省略其他方法和属性
public Byte[] send(String address, Byte[] data){
//...
}
}
public class HtmlDownloader{
private NetworkTransporter transporter;
public Html downloadHtml(String url){
HtmlRequest htmlRequest = new HtmlRequest(url);
Byte[] rawHtml = transporter.send(htmlRequest.getAddress(),htmlRequest.getContent.getBytes());
return new Html(rawHtml);
}
}
public class Document{
private Html html;
private String url;
public Document(String url, Html html){
this.html = html;
this.url = url;
}
// ...
public Document(String url){
this.url = url;
HtmlDownloader downloader = new HtmlDownloader();
this.html = downloader.downloadHtml(url);
}
}
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);
}
}