一、迪米特法则的定义

迪米特法则,也称为最少知识原则,虽然名字不同,但描述的是同一个规则:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,被耦合或调用的类的内部是如何复杂都和我没关系,我就知道你提供的这么多public方法,我就调用这么多,其他的我一概不关心。

二、迪米特法则的含义

迪米特法则对类的低耦合提出了明确的规定,其包含以下几层含义。

(一)、只和朋友交流

迪米特法则,要求只与直接朋友通信。什么叫直接朋友?每个对象都必然会与其他对象有耦合关系,两个对象之间的耦合就成为朋友关系,这种关系的类型有很多,例如组合、聚合、依赖等。下面我们举例说明如何才能做到只与直接朋友进行交流。

举例:老师让体育委员确认一下全班女生是否都到齐?类图如下图所示:

Java面向对象设计原则之迪米特法则介绍

其实现过程如下代码:

老师类:

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的健壮性。

问题已经发现,我们修改一下程序,将类图稍作调整,如下图所示:

Java面向对象设计原则之迪米特法则介绍

修改后的老师类:

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,再然后选择安装目录...这是一个典型的顺序执行动作,具体到程序中就是:调用一个或多个类,先执行第一个方法,然后是第二个方法,根据返回结果再来看是否可以调用第三个方法,或者第四个方法,等等。其类图大体如下:

Java面向对象设计原则之迪米特法则介绍

实现过程如下:

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类,从而把修改变更的风险扩散开了。因此,这种耦合是不合适的,我们需要对设计进行重构,重构后的类图如下:

Java面向对象设计原则之迪米特法则介绍

在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关键字等。

(三)、是自己的就是自己的

在实际应用中经常会出现这样一个方法:放在本类中也可以,放在其他类也没有错,那怎么去衡量呢?可以检查这样一个原则:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。

三、总结

迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度。读者在采用迪米特法则的时候需要反复权衡,既做到让结构清晰,又做到高内聚低耦合。

原文地址:https://weishihuai.blog.csdn.net/article/details/120711308

相关文章: