Bootstrap

设计原则之迪米特原则

迪米特原则 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);
	}
}
;