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

2023-02-14 12:02:39 原则 面向对象 法则

一、迪米特法则的定义

迪米特法则,也称为最少知识原则,虽然名字不同,但描述的是同一个规则:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,被耦合或调用的类的内部是如何复杂都和我没关系,我就知道你提供的这么多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());
    
    public int first() {
        System.out.println("执行第一个方法");
        return random.nextInt(100);
    }
    
    public int second() {
        System.out.println("执行第二个方法");
        return random.nextInt(100);
    }
    
    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());
    
    private int first() {
        System.out.println("执行第一个方法");
        return random.nextInt(100);
    }
    
    private int second() {
        System.out.println("执行第二个方法");
        return random.nextInt(100);
    }
    
    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关键字等。

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

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

三、总结

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

到此这篇关于Java面向对象设计原则之迪米特法则介绍的文章就介绍到这了,更多相关Java迪米特法则内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章