• 设计模式—— 五:迪米特原则



    @


    什么是迪米特原则?


    迪米特法则来自于1987年美国东北大学(Northeastern University)一个名为“Demeter”的研究项目。迪米特法则又称为最少知识原则(LeastKnowledge Principle, LKP),其定义如下:
    迪米特法则(Law of Demeter, LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。

    如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。


    迪米特法则的含义

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


    1. 只和朋友交流

    迪米特法则还有一个英文解释是:Only talk to your immediate friends(只与直接的朋友通信。)什么叫做直接的朋友呢?每个对象都必然会与其他对象有耦合关系,两个对象之间的 耦合就成为朋友关系,这种关系的类型有很多,例如组合、聚合、依赖等。

    以老师要求体育委员清点全班女生为例:


    不遵循迪米特法则的定义


    5-1:老师要求清点女生类图

    在这里插入图片描述

    老师类:

    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 Girl { 
    }
    

    场景类:

    public class Client { 
       public static void main(String[] args) { 
         Teacher teacher= new Teacher(); 
         //老师发布命令 
         teacher.commond(new GroupLeader()); 
       } 
    }
    

    运行结果:

    女生数量是:20
    

    体育委员按照老师的要求对女生进行了清点,并得出了数量。这个程序有什么问题,首先确定Teacher类有几个朋友类,它仅有一个朋友类—— GroupLeader。为什么Girl不是朋友类呢?Teacher也对它产生了依赖关系呀!朋友类的定义是 这样的:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类,而Girl这个类就是出现在commond方法体内,因此不属于Teacher类的 朋友类。迪米特法则告诉我们一个类只和朋友类交流,但是刚刚定义的commond方法却 与Girl类有了交流,声明了一个List动态数组,也就是与一个陌生的类Girl有了交流, 这样就破坏了Teacher的健壮性。方法是类的一个行为,类竟然不知道自己的行为与其他类 产生依赖关系,这是不允许的,严重违反了迪米特法则。


    遵循迪米特法则的定义


    现在根据迪米特法则进行改造,去掉Teacher对Girl类的依赖关系。


    5-2:修改后的老师要求清点女生类图

    在这里插入图片描述

    修改后的老师类:

    public class Teacher { 
      //老师对学生发布命令,清一下女生 
      public void commond(GroupLeader groupLeader){ 
      //告诉体育委员开始执行清查任务 
      groupLeader.countGirls(); 
      } 
    }
    

    修改后的体育委员类:

    public class GroupLeader { 
      private List<Girl> listGirls; //传递全班的女生进来 
      public GroupLeader(List<Girl> _listGirls){ 
        this.listGirls = _listGirls; 
      }
      //清查女生数量 
      public void countGirls(){ 
        System.out.println("女生数量是:"+this.listGirls.size()); 
      } 
    
    }
    

    修改后的场景类:

    public class Client { 
      public static void main(String[] args) { 
        //产生一个女生群体 
        List<Girl> listGirls = new ArrayList<Girl>(); 
        //初始化女生 
        for(int i=0;i<20;i++){ 
          listGirls.add(new Girl()); 
        }
        Teacher teacher= new Teacher(); 
        //老师发布命令 
        teacher.commond(new GroupLeader(listGirls)); 
      } 
    }
    

    把Teacher中对List的初始化移动到了场景类中,同时在GroupLeader中增加了对Girl的注入,避开了Teacher类对陌生类Girl的访问,降低了系统间的耦合,提高了系统的健壮性。


    2. 朋友间也是有距离的


    朋友类之间可以交流,但也不能无话不谈。

    以安装软件为例:第一步是确认是否安装,第二步确认 License,再然后选择安装目录……这是一个典型的顺序执行动作,具体到程序中就是:调用 一个或多个类,先执行第一个方法,然后是第二个方法,根据返回结果再来看是否可以调用 第三个方法,或者第四个方法。


    不遵循迪米特原则的设计


    5-3:软件安装过程类图

    在这里插入图片描述

    导向类:
    定义步骤方法。

    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); 
      } 
    
    }
    

    软件安装InstallSoftware类:

    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类,两者的朋友关系太亲密了,耦合关系变得异 常牢固。如果要将Wizard类中的first方法返回值的类型由int改为boolean,就需要修改 InstallSoftware类,从而把修改变更的风险扩散开了。因此,这样的耦合是极度不合适的。


    遵循迪米特原则的设计


    5-4:重构后的软件安装过程类图

    在这里插入图片描述


    修改后的导向类:
    在Wizard类中增加一个installWizard方法,对安装过程进行封装,同时把原有的三个 public方法修改为private方法。将三个步骤的访问权限修改为private,同时把InstallSoftware中的方法installWizad移动到 Wizard方法中。通过这样的重构后,Wizard类就只对外公布了一个public方法,即使要修改 first方法的返回值,影响的也仅仅只是Wizard本身,其他类不受影响,这显示了类的高内聚特性。

    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(); 
            } 
          } 
        } 
      } 
    
    }
    

    修改后的InstallSoftware类:

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

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


    3. 是自己的就是自己的


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


    4. 谨慎使用Serializable





    ⇐⇐ 设计模式—— 四:接口隔离原则


    参考:

    【1】:《设计模式之禅》
    【2】:面向对象设计原则之迪米特法则
    【3】:《大话设计模式》
    【4】:白话设计——浅谈迪米特法则

  • 相关阅读:
    Shiro自定义密码匹配认证
    logback 发送邮件和自定义发送邮件;java类发送邮件
    webVR全景图多种方案实现(pannellum,aframe,Krpano,three,jquery-vrview)
    前端接受后端文件流并下载的几种方法
    回流(reflow)与重绘(repaint)
    JS数组去重的几种常见方法
    React 生命周期
    浅谈React工作原理
    如何在Vue项目中使用vw实现移动端适配
    移动端web整理 移动端问题总结,移动web遇到的那些坑
  • 原文地址:https://www.cnblogs.com/three-fighter/p/12353205.html
Copyright © 2020-2023  润新知