定义
只与直接朋友交谈,不和陌生人说话。其软件含义是:如果两个软件实体无需直接通信,那么就不应当发生直接调用关系,而是应该通过第三方转发调用。
理解迪米特法则的关键在于定义中说明的两个词:朋友、说话。“说话”的含义是指类之间的调用行为,即所谓软件实体之间的通信。对于复杂的业务需求,可能产生比较长的通信链路。在通信的过程中,依照迪米特法则,要先判断对方是不是当前类的朋友,能够称得上朋友的软件实体包括以下:
- 当前类对象本身(this)
- 当前类的任何组件(成员变量)
- 当前类的方法参数(输入参数、方法返回值)
- 当前类方法创建或实例化的对象(new创建)
在方法内部,通过调用其他类的方法返回的对象不是当前类的朋友。
作用
从定义可知,迪米特法则限制了软件实体之间的通信深度,缩短了通信链路长度,强调了类的独立性。其优点如下
- 降低类之间的耦合度,提高模块的独立性。
- 独立性提高,则复用性、可维护性、可扩展性也提高。
- 独立性提高,出现错误之后,影响范围缩小。
实现方法
从命名可知,迪米特法则较其他原则具备更高的优先级。在设计类时,尽量较少类同其他外部的交流。设计或评价类是否符合迪米特法则的步骤主要两步:
- 当前类需要使用某一外部类时,判断外部类是否是当前类的朋友。
- 如果是朋友,则可以正常使用。
- 如果不是朋友,创建第三方类作为通信中介,连通当前类和外部类。
在运用迪米特法则是要注意以下5点:
-
迪米特法则需要第三方类做通信中介,可能产生类爆炸问题,要权衡是否创建新的第三方类。
-
尽量减少引用其他对象的次数。多一事不入少一事,引用越少,出现问题的风险越小。
-
减低类成员的访问权限,使其他类不能调用类成员。
-
设计类方法时,如果该方法不会增加类间关系,则对本类没有影响,则可以添加该方法。
-
判断某类的设计是否符合迪米特法则时,可观察此类的import部分,如果导入的外部类不是当前类的朋友,则说明当前类不符合迪米特法则。
代码实践
业务场景: 老板问项目组长某位员工的名字。
以下是不符合迪米特法则的设计:
//老板类
//此时老板需要拿到员工类,让员工打印名字。
//但员工类不是老板类的朋友,因此这个老板类的设计不符合迪米特法则。
public class Boss {
public void getEmloye(TeamLeader teamLeader){
//此处的employ是其他对象方法的返回值,不属于boss的朋友
Employe employ = teamLeader.getEmploy();
System.out.println(employ.getName());
}
}
//项目组长类
public class TeamLeader {
//返回一个员工对象
public Employe getEmploy(){
return new Employe("张三");
}
}
//员工类
public class Employe {
String name;
Employe(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
观察以上代码,可对Boss类进行改造,将Employe类从Boss类中移除至第三方通信中介TeamLeader类中,新的类视图如下,
//老板类
public class Boss {
public void getEmloye(TeamLeader teamLeader){
//同员工通信细节完全交给项目组长实现
//老板类不直接同员工类通信
teamLeader.getEmploy();
}
}
//项目组长类
public class TeamLeader {
public void getEmploy(){
System.out.println(new Employe("张三").getName());
}
}
//员工类
public class Employe {
String name;
Employe(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
对比以上两个版本的代码可以发现,出现不符合迪米特法则的类出现在较高层,即调用其他软件实体的高层类。并且,改造这些类一般是将不符合迪米特法则的代码块移动到第三方类中,代码块原有的功能不发生变化。
编程知识点
-
java语言通过位置(同包/跨包)、继承关系(子类/非子类)两个维度控制访问权限,可产生四种作用域组合关系,包括同包子类、同包非子类、跨包子类、跨包非子类,其中跨包非子类的“亲密关系”最薄弱。在确定访问权限时,一个维度确定两个组合。譬如同包权限,则指同包子类和同包非子类都可以访问。
- private,私有权限。类内部可访问,子类不能访问,不能跨包访问。单一限制位置,只有类内部成员。
- default,默认权限或包权限。同一个包内可以访问。单一限制位置,只有同包(同包子类/同包非子类)内部。
- protected,保护访问权限。类内部可访问,同包(同包子类/同包非子类)可以访问,子类(同包子类/跨包子类)可以访问,跨包的非子类不能访问。组合限制,位置->同包(子类/非子类)可以访问,位置关系->同包子类可以访问.
记忆点:只有最远的亲戚(跨包非子类)不能访问。
- public,公共权限。任何位置、任何类可以访问。无限制。
特别地,private和protected不能修饰一般的外部类,否则编译就会报“modifier private not allowed here”,内部类另当别论。
- 同一个项目下多个模块的src是连通的,直接查看src的类图会显示全部src下的类。规范做法是在src下创建自己的package,将类放在包中,此时查看包的类图即可看到包中所有类关系.