天天看点

设计模式-迪米特法则(5)

设计原则

单一职责原则
里氏替换原则
依赖倒置原则
接口隔离原则
迪米特法则
开闭原则
           

迪米特法则定义

一个对象应该对其他对象有最少的了解。
           

通俗地讲, 一个类应该对自己需要耦合或调用的类知道得最少, 你(被耦合或调用的类) 的内部是如何复杂都和我没关系, 那是你的事情, 我就知道你提供的这么多public方法, 我就调用这么多, 其他的我一概不关心。

迪米特法则含义

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

1. 只和朋友交流

只与直接的朋友通信。什么叫做直接的朋友呢? 每个对象都必然会与其他对象有耦合关系, 两个对象之间的耦合就成为朋友关系, 这种关系的类型有很多, 例如组合、 聚合、 依赖等。

下面我们将举例说明如何才能做到只与直接的朋友交流。

设计模式-迪米特法则(5)
public class Teacher {
    //老师对学生发布命令, 清一下女生
    public void commond(GroupLeader groupLeader){
        List listGirls = new ArrayList();
        //初始化女生
        for(int i=0;i<20;i++){
            listGirls.add(new Girl());
        }
        //告诉体育委员开始执行清查任务
        groupLeader.countGirls(listGirls);
    }
}
           
public class GroupLeader {
    //清查女生数量
    public void countGirls(List<Girl> listGirls){
        System.out.println("女生数量是: "+listGirls.size());
    }
}
           
public class Client {
    public static void main(String[] args) {
        Teacher teacher= new Teacher();
        //老师发布命令
        teacher.commond(new GroupLeader());
    }
}
           

首先确定Teacher类有几个朋友类, 它仅有一个朋友类——GroupLeader。

为什么Girl不是朋友类呢? Teacher也对它产生了依赖关系呀!

朋友类的定义是这样的: 出现在成员变量、 方法的输入输出参数中的类称为成员朋友类, 而出现在方法体内部的类不属于朋友类, 而Girl这个类就是出现在commond方法体内, 因此不属于Teacher类的朋友类。

2. 朋友间也是有距离的

人和人之间是有距离的, 太远关系逐渐疏远, 最终形同陌路; 太近就相互刺伤。 对朋友关系描述最贴切的故事就是: 两只刺猬取暖, 太远取不到暖, 太近刺伤了对方, 必须保持一个既能取暖又不刺伤对方的距离。 迪米特法则就是对这个距离进行描述, 即使是朋友类之间也不能无话不说, 无所不知。

我们在安装软件的时候, 经常会有一个导向动作, 第一步是确认是否安装, 第二步确认License, 再然后选择安装目录……这是一个典型的顺序执行动作, 具体到程序中就是: 调用一个或多个类, 先执行第一个方法, 然后是第二个方法, 根据返回结果再来看是否可以调用第三个方法, 或者第四个方法, 等等, 其类图如图:

设计模式-迪米特法则(5)
public class Wizard {private Random rand = new Random(System.currentTimeMillis());
    //第一步
    public int first(){
        System.out.println("执行第一个方法...");
        return rand.nextInt(100);
    }
    //第二步
    public int second(){
        System.out.println("执行第二个方法...");
        return rand.nextInt(100);
    }
    //第三个方法
    public int third(){
        System.out.println("执行第三个方法...");
        return rand.nextInt(100);
    }
}
           
public class InstallSoftware {
    public void installWizard(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 invoker = new InstallSoftware();invoker.installWizard(new Wizard());
    }
}
           

程序虽然简单, 但是隐藏的问题可不简单, 思考一下程序有什么问题。Wizard类把太多的方法暴露给InstallSoftware类, 两者的朋友关系太亲密了, 耦合关系变得异常牢固。因此, 这样的耦合是极度不合适的, 我们需要对设计进行重构, 重构后的类图如图:

设计模式-迪米特法则(5)

在Wizard类中增加一个installWizard方法, 对安装过程进行封装, 同时把原有的三个public方法修改为private方法,代码如下:

public class Wizard {
    private Random rand = new Random(System.currentTimeMillis());
    //第一步
    private int first(){
        System.out.println("执行第一个方法...");
        return rand.nextInt(100);
    }
    //第二步
    private int second(){
        System.out.println("执行第二个方法...");
        return rand.nextInt(100);
    }
    //第三个方法
    private int third(){
        System.out.println("执行第三个方法...");return rand.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中的方法installWizad移动到Wizard方法中。 通过这样的重构后, Wizard类就只对外公布了一个public方法, 即使要修改first方法的返回值, 影响的也仅仅只是Wizard本身, 其他类不受影响, 这显示了类的高内聚特性。

对InstallSoftware类进行少量的修改,代码如下:

public class InstallSoftware {
    public void installWizard(Wizard wizard){
        //直接调用
        wizard.installWizard();
    }
}
           

场景类Client没有任何改变,  通过进行重构, 类间的耦合关系变弱了, 结构也清晰了, 变更引起的风险也变小了。

一个类公开的public属性或方法越多, 修改时涉及的面也就越大, 变更引起的风险扩散也就越大。 因此, 为了保持朋友类间的距离, 在设计时需要反复衡量: 是否还可以再减少public方法和属性, 是否可以修改为private、 package-private(包类型, 在类、 方法、 变量前不加访问权限, 则默认为包类型) 、 protected等访问权限, 是否可以加上final关键字等。

3. 是自己的就是自己的

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

4. 谨慎使用Serializable

在一个项目中使用RMI(Remote Method Invocation, 远程方法调用) 方式传递一个VO(Value Object, 值对象) , 这个对象就必须实现Serializable接口(仅仅是一个标志性接口, 不需要实现具体的方法) , 也就是把需要网络传输的对象进行序列化, 否则就会出现NotSerializableException异常。 突然有一天, 客户端的VO修改了一个属性的访问权限, 从private变更为public, 访问权限扩大了, 如果服务器上没有做出相应的变更, 就会报序列化失败, 就这么简单。

最佳实践

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

继续阅读