迪米特法则
什么是迪米特法则(Law of Demeter, LoD)
迪米特法则也可以称为最少知识法则(least Knowledge principle, LKP)。它们都描述了一个规则:一个对象应该对其他对象有最少的了解。通俗来说,一个类应该对自己需要耦合或调用的类知道最少,也就是对于被依赖的类,向外公开的方法应该尽可能的少。
迪米特法则还有一种解释:Only talk to your immediate friends,只与直接朋友进行通信。关于朋友给出如下解释:两个对象之间的耦合关系称之为朋友,通常有依赖,关联,聚合,组成等。而直接朋友通常表现为关联,聚合和组成关系,即两个对象之间联系更为紧密,通常以成员变量,方法参数和返回值的形式出现。
LoD实例演示
迪米特法则强调了下面两点:
- 从被依赖者的角度:只暴露应该暴露的方法或属性,即编写相关的类时确定方法和属性的权限
- 从依赖者的角度来看,只依赖应该依赖的对象
先举例演示第一点:当我们按下计算机的按钮的时候,计算机会指行一系列操作:保存当前任务,关闭相关服务,接着关闭显示屏,最后关闭电源,这些操作完成则计算机才算关闭。如下是代码示例:
//计算机类
public class Computer{
public void saveCurrentTask(){
//do something
}
public void closeService(){
//do something
}
public void closeScreen(){
//do something
}
public void closePower(){
//do something
}
public void close(){
saveCurrentTask();
closeService();
closeScreen();
closePower();
}
}
//人
public class Person{
private Computer c;
...
public void clickCloseButton(){
//现在你要开始关闭计算机了,正常来说你只需要调用close()方法即可,
//但是你发现Computer所有的方法都是公开的,该怎么关闭呢?于是你写下了以下关闭的流程:
c.saveCurrentTask();
c.closePower();
c.close();
//亦或是以下的操作
c.closePower();
//还可能是以下的操作
c.close();
c.closePower();
}
}
观察上面的代码我们发现了什么问题:对于人来说,我期待的结果只是按下关闭电钮然后计算机“啪”的给我关了,而不是需要我去小心的去保存当前正在执行的任务等等。在上面的代码中,c是一个完全暴露的对象,它的方法是完全公开的,对于Person来说,手里面就如同多出了好几把钥匙,至于具体用哪一把他不知道,所以只能一把一把的去试一遍,显然这样的设计是不对的。
根据迪米特法则的第一点:从被依赖者的角度,只暴露应该暴露的方法。在本例中,应该暴露的方法就是close(),关于计算机的其他操作不是依赖者应该关注的问题,应该对依赖者关闭,重新设计如下:
//计算机类
public class Computer{
private void saveCurrentTask(){
//do something
}
private void closeService(){
//do something
}
private void closeScreen(){
//do something
}
private void closePower(){
//do something
}
public void close(){
saveCurrentTask();
closeService();
closeScreen();
closePower();
}
}
//人
public class Person{
private Computer c;
...
public void clickCloseButton(){
c.close();
}
}
现在举例演示第二点:在我们生活中会有这样的情况,比如张三去找李四帮忙做一件事,对于李四来说这件事也很棘手,李四也做不了,但是李四有一个好哥们王五却能完成这件事,所以李四就把这件事交给王五去办(在本例中,张三和王五是不认识的)。现在我们暂定张三为A,李四为B,王五为C,代码示例如下:
//张三找李四办事
public class A {
public String name;
public A(String name) {
this.name = name;
}
public B getB(String name) {
return new B(name);
}
public void work() {
B b = getB("李四");
C c = b.getC("王五");
c.work();
}
}
//李四办不了于是去找王五
public class B {
private String name;
public B(String name) {
this.name = name;
}
public C getC(String name) {
return new C(name);
}
}
//对于王五来说so easy,办得妥妥的
public class C {
public String name;
public C(String name) {
this.name = name;
}
public void work() {
System.out.println(name + "把这件事做好了");
}
}
public class Client {
public static void main(String[] args) {
A a = new A("张三");
a.work();
}
}
结果:王五把事情做好了
上面的设计输出答案是正确的,王五确实把事情办妥了。但是我们仔细看业务逻辑确发现这样做事不对的。张三和王五互相不认识,那为什么代表张三的A类中会有代表李四的C类呢?这样明显是违背了迪米特法则的。现在我们对上面的代码进行重构,根据迪米特法则的第二点:从依赖者的角度来看,只依赖应该依赖的对象。在本例中,张三只认识李四,那么只能依赖李四。重构后代码如下:
//张三认识李四,只依赖李四
public class A {
public String name;
public A(String name) {
this.name = name;
}
public B getB(String name) {
return new B(name);
}
public void work() {
B b = getB("李四");
b.work();
}
}
//李四依赖王五
public class B {
private String name;
public B(String name) {
this.name = name;
}
public C getC(String name) {
return new C(name);
}
public void work(){
C c = getC("王五");
c.work();
}
}
//王五把事情办得妥妥的
public class C {
public String name;
public C(String name) {
this.name = name;
}
public void work() {
System.out.println(name + "把这件事做好了");
}
}
public class Client {
public static void main(String[] args) {
A a = new A("张三");
a.work();
}
}
结果:王五把事情做好了
总结
迪米特法则的目的是让类之间解耦,降低耦合度,提高类的复用性。但是设计原则并非有利无弊,使用迪米特法则会产生大量的中转类或跳转类,导致系统复杂度提高。在实际的项目中,需要适度的考虑这个原则,不能因为套用原则而反而使项目设计变得复杂。
参考书籍与链接
- 《设计模式之禅》
- https://tianweili.github.io/2015/02/12/设计模式六大原则-迪米特法则/
- https://blog.csdn.net/zhengzhb/article/details/7296930
- https://www.jianshu.com/p/30931aab5ea0