Java面向对象设计原则之迪米特法则介绍
每天都要进步一点点 人气:0一、迪米特法则的定义
迪米特法则,也称为最少知识原则,虽然名字不同,但描述的是同一个规则:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,被耦合或调用的类的内部是如何复杂都和我没关系,我就知道你提供的这么多public方法,我就调用这么多,其他的我一概不关心。
二、迪米特法则的含义
迪米特法则对类的低耦合提出了明确的规定,其包含以下几层含义。
(一)、只和朋友交流
迪米特法则,要求只与直接朋友通信。什么叫直接朋友?每个对象都必然会与其他对象有耦合关系,两个对象之间的耦合就成为朋友关系,这种关系的类型有很多,例如组合、聚合、依赖等。下面我们举例说明如何才能做到只与直接朋友进行交流。
举例:老师让体育委员确认一下全班女生是否都到齐?类图如下图所示:
其实现过程如下代码:
老师类:
public class Teacher { //老师发出命令,清点一下女生人数 public void command(GroupLeader groupLeader) { List<Girl> girls = new ArrayList<>(); for (int i = 0; i < 10; i++) { girls.add(new Girl()); } //告诉体育委员开始执行清查任务 groupLeader.countGirls(girls); } }
老师只有一个方法command,先定义出所有的女生,然后发布命令给体育委员,去清点女生的人数。
体育委员GroupLeader的代码如下:
public class GroupLeader { public void countGirls(List<Girl> girlList) { System.out.println("女生数量: " + girlList.size()); } }
老师类和体育委员类都对Girl类产生依赖,而且女生类不需要执行任何动作,因此定义如下:
public class Girl { }
再定义一个场景类:
public class Client { public static void main(String[] args) { Teacher teacher = new Teacher(); teacher.command(new GroupLeader()); } }
运行结果如下:
女生数量: 10
体育委员按照老师的要求对女生进行了清点,并得出了数量。我们回过头来思考一下这个程序有什么问题,首先确定Teacher类有几个朋友类,它仅有一个朋友类-------GroupLeader,为什么Girl不是朋友类呢?Teacher对Girl类产生了依赖啊,朋友类的定义是这样的:出现在成员变量、方法的输入参数、输出参数中的类成为成员朋友类。而Gril这个类是出现在command方法内部的,因此不属于Teacher的直接朋友。
迪米特法则告诉我们一个类只与朋友类交流,但是我们刚刚定义的command方法却与Girl类有了交流,声明了List<Girl>动态集合,这样就破坏了Teacher的健壮性。
问题已经发现,我们修改一下程序,将类图稍作调整,如下图所示:
修改后的老师类:
public class Teacher { //老师发出命令,清点一下女生人数 public void command(GroupLeader groupLeader) { //告诉体育委员开始执行清查任务 groupLeader.countGirls(); } }
修改后的GroupLeader体育委员类:
public class GroupLeader { private List<Girl> girls; public GroupLeader(List<Girl> girls) { this.girls = girls; } public void countGirls() { System.out.println("女生数量: " + girls.size()); } }
在GroupLeader类中定义了一个构造函数,通过构造函数传递了依赖关系。同时,对场景类也进行了一些调整:
public class Client { public static void main(String[] args) { List<Girl> girls = new ArrayList<>(); //初始化女生信息 for (int i = 0; i < 10; i++) { girls.add(new Girl()); } ; Teacher teacher = new Teacher(); //老师发布命令 teacher.command(new GroupLeader(girls)); } }
对程序进行了简单的修改,把Teacher的List<Girl>的初始化移动到了场景类中,同时在GroupLeader中增加对Girl的注入,避开了Teacher对陌生类Girl的访问,降低了系统间耦合,提高了系统的健壮性。
(二)、朋友间也是有距离的
人和人之间是有距离的,太远关系逐渐疏远,最终形同陌路;太近就相互刺伤。迪米特法则就是对这个距离进行描述,即使是朋友类之间也不能无话不说,无所不知。
我们在安装软件的时候,经常会有一个导向动作,第一步是确认是否安装,第二步确认Lisence,再然后选择安装目录...这是一个典型的顺序执行动作,具体到程序中就是:调用一个或多个类,先执行第一个方法,然后是第二个方法,根据返回结果再来看是否可以调用第三个方法,或者第四个方法,等等。其类图大体如下:
实现过程如下:
public class Wizard { private Random random = new Random(System.currentTimeMillis()); /** * 第一步 * * @return */ public int first() { System.out.println("执行第一个方法"); return random.nextInt(100); } /** * 第二步 * * @return */ public int second() { System.out.println("执行第二个方法"); return random.nextInt(100); } /** * 第三步 * * @return */ public int third() { System.out.println("执行第三个方法"); return random.nextInt(100); } }
在Wizard类中分别定义了三个步骤方法,每个步骤中都有相关的业务逻辑完成指定的任务,我们使用一个随机函数来代替业务执行的返回值。
InstallSoftware类的代码如下:
public class InstallSoftware { public void install(Wizard wizard) { int first = wizard.first(); //根据first的返回结果,看是否需要执行second if (first > 50) { int second = wizard.second(); if (second > 50) { int third = wizard.third(); if (third > 50) { wizard.first(); } } } } }
根据每个方法执行的结果决定是否继续执行下一个方法,模拟人工的选择操作。场景类如下:
public class Client { public static void main(String[] args) { InstallSoftware installSoftware = new InstallSoftware(); installSoftware.install(new Wizard()); } }
以上程序很简单,运行结果和随机数有关,每次执行的结果都不相同,需要读者自己运行并查看结果。程序虽然简单,但隐藏的问题可不简单,思考一下程序有什么问题?
Wizard类把太多的方法暴露给InstallSoftware类,两者的朋友关系太亲密了,耦合关系变得异常牢固。如果要将Wizard类中的first方法返回值的类型由int修改为boolean,就需要修改InstallSoftware类,从而把修改变更的风险扩散开了。因此,这种耦合是不合适的,我们需要对设计进行重构,重构后的类图如下:
在Wizard类中增加一个installWizard方法,对安装过程进行封装,同时把所有的三个public方法修改为private方法,如下:
public class Wizard { private Random random = new Random(System.currentTimeMillis()); /** * 第一步 * * @return */ private int first() { System.out.println("执行第一个方法"); return random.nextInt(100); } /** * 第二步 * * @return */ private int second() { System.out.println("执行第二个方法"); return random.nextInt(100); } /** * 第三步 * * @return */ private int third() { System.out.println("执行第三个方法"); return random.nextInt(100); } public void installWizard() { int first = this.first(); //根据first的返回结果,看是否需要执行second if (first > 50) { int second = this.second(); if (second > 50) { int third = this.third(); if (third > 50) { this.first(); } } } } }
讲啊三个步骤的访问权限修改为private,同时把InstallSoftware中的方法installWizard()移动到了Wizard类中。通过这样的重构后,Wizard类就只对外公布了一个public方法,即使要修改first方法的返回值,影响的也仅仅是Wizard本身,其他类不受影响,这显示了类的高内聚特性。
修改后的InstallSoftware代码如下:
public class InstallSoftware { public void install(Wizard wizard) { wizard.installWizard(); } }
场景类没有任何改变,通过进行重构,类间的耦合关系变弱了,结构也清晰了,变更引起的风险也变小了。
一个类公开的public属性或者方法越多,修改时涉及的面就越大,变更引起的风险扩散也就越大。因此,为了保持朋友间的距离,在设计时需要反复衡量:是否还可以再减少public属性和方法,是否可以修改为private、package-private、protected等访问权限,是否可以加上final关键字等。
(三)、是自己的就是自己的
在实际应用中经常会出现这样一个方法:放在本类中也可以,放在其他类也没有错,那怎么去衡量呢?可以检查这样一个原则:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。
三、总结
迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度。读者在采用迪米特法则的时候需要反复权衡,既做到让结构清晰,又做到高内聚低耦合。
加载全部内容