zoukankan      html  css  js  c++  java
  • 设计模式—— 五:迪米特原则


    @


    什么是迪米特原则?


    迪米特法则来自于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】:白话设计——浅谈迪米特法则

  • 相关阅读:
    leetcode231 2的幂 leetcode342 4的幂 leetcode326 3的幂
    leetcode300. Longest Increasing Subsequence 最长递增子序列 、674. Longest Continuous Increasing Subsequence
    leetcode64. Minimum Path Sum
    leetcode 20 括号匹配
    算法题待做
    leetcode 121. Best Time to Buy and Sell Stock 、122.Best Time to Buy and Sell Stock II 、309. Best Time to Buy and Sell Stock with Cooldown 、714. Best Time to Buy and Sell Stock with Transaction Fee
    rand7生成rand10,rand1生成rand6,rand2生成rand5(包含了rand2生成rand3)
    依图
    leetcode 1.Two Sum 、167. Two Sum II
    从分类,排序,top-k多个方面对推荐算法稳定性的评价
  • 原文地址:https://www.cnblogs.com/three-fighter/p/12353205.html
Copyright © 2011-2022 走看看