07.迪米特原则介绍

07.迪米特原则介绍

目录介绍
  • 01.问题思考的分析
  • 02.学习迪米特原则目标
  • 03.理解迪米特原则
  • 04.迪米特原则思想
  • 05.迪米特原则案例1
  • 06.迪米特原则案例2
  • 07.迪米特原则思考
  • 08.迪米特原则的总结

01.问题思考的分析

  1. 什么是迪米特原则,这个原则如何理解,如何运用到实际开发,举例说明一下?
  2. 什么是高内聚松耦合,能否举例说明一下?

迪米特法则。尽管它不像 SOLID、KISS、DRY 原则那样,人尽皆知,但它却非常实用。利用这个原则,能够帮我们实现代码的"高内聚、松耦合"。

今天,就围绕下面几个问题,并结合两个代码实战案例,来深入地学习这个法则。

  • 什么是"高内聚、松耦合"?
  • 如何利用迪米特法则来实现"高内聚、松耦合"?
  • 有哪些代码设计是明显违背迪米特法则的?对此又该如何重构?

02.学习迪米特原则目标

迪米特原则(Law of Demeter)也被称为最少知识原则(Principle of Least Knowledge),是面向对象设计中的一个重要原则。

能够清晰知道该原则的思想,并且可以应用到实际代码中,学习的目标是降低类之间的耦合性,提高代码的可维护性和可扩展性。

03.理解迪米特原则

3.1 迪米特原则介绍

迪米特法则的英文翻译是:Law of Demeter,缩写是 LOD。不过,它还有另外一个更加达意的名字,叫作最小知识原则,英文翻译为:The Least Knowledge Principle。

关于这个设计原则,我们先来看一下它最原汁原味的英文定义:

Each unit should have only limited knowledge about other units: only units "closely" related to the current unit. Or: Each unit should only talk to its friends; Don't talk to strangers.

把它直译成中文: 每个模块(unit)只应该了解那些与它关系密切的模块(units: only units "closely" related to the current unit)的有限知识(knowledge)。或者说,每个模块只和自己的朋友"说话"(talk),不和陌生人"说话"(talk)。

3.2 迪米特原则由来

类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。这个时候要降低类之间耦合!

3.3 内聚和耦合关系

"高内聚"有助于"松耦合",同理,"低内聚"也会导致"紧耦合"。

类的粒度比较小,每个类的职责都比较单一。相近的功能都放到了一个类中,不相近的功能被分割到了多个类中。这样类更加独立,代码的内聚性更好。因为职责单一,所以每个类被依赖的类就会比较少,代码低耦合。一个类的修改,只会影响到一个依赖类的代码改动。我们只需要测试这一个依赖类是否还能正常工作就行了。

类粒度比较大,低内聚,功能大而全,不相近的功能放到了一个类中。这就导致很多其他类都依赖这个类。当我们修改这个类的某一个功能代码的时候,会影响依赖它的多个类。这也就是所谓的"牵一发而动全身"。

3.3 何为高内聚松耦合

"高内聚、松耦合"是一个非常重要的设计思想。可以看案例视频播放器View如何和Player解耦合

能够有效地提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。很多设计原则都以实现代码的"高内聚、松耦合"为目的,比如单一职责原则、基于接口而非实现编程等。

在这个设计思想中,"高内聚"用来指导类本身的设计,"松耦合"用来指导类与类之间依赖关系的设计。

所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中,代码容易维护。

所谓松耦合是说,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动不会或者很少导致依赖类的代码改动。

04.迪米特原则思想

迪米特原则的核心观念 : 就是类之间的解耦,解耦是有一定程度的,尽量做到弱耦合,耦合程度越低,类的复用率才能提高。由于减少了类之间不必要的依赖,从而达到了降低了耦合的目的。

迪米特法则的原理是通过封装和信息隐藏来实现对象之间的松耦合。每个对象只需要知道与之直接交互的对象的接口,而不需要了解对象的内部实现细节。

05.迪米特原则案例1

例子:有一个集团公司,下属单位有分公司和直属部门,现在要求打印出所有下属单位的员工ID。先来看一下违反迪米特法则的设计。

java 复制代码
// 总公司员工
class Employee {
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

// 分公司员工
class SubEmployee {
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

// 子公司管理
class SubCompanyManager {
    public List<SubEmployee> getAllEmployee() {
        List<SubEmployee> list = new ArrayList<>();
        for (int i = 1; i < 5; i++) {
            SubEmployee subEmployee = new SubEmployee();
            // 给分公司人员顺序分配一个ID
            subEmployee.setId("分公司" + i);
            list.add(subEmployee);
        }
        return list;
    }
}

// 总公司管理
class CompanyManager {
    public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<>();
        for (int i = 1; i < 3; i++) {
            Employee employee = new Employee();
            // 给总公司人员顺序分配一个ID
            employee.setId("总公司" + i);
            list.add(employee);
        }
        return list;
    }

    public void printAllEmployee(SubCompanyManager subCompanyManager) {
        // 分公司员工
        List<SubEmployee> subEmployeeList = subCompanyManager.getAllEmployee();
        for (SubEmployee subEmployee : subEmployeeList) {
            System.out.println(subEmployee.getId());
        }

        // 总公司员工
        List<Employee> employeeList = getAllEmployee();
        for (Employee employee : employeeList) {
            System.out.println(employee.getId());
        }
    }
}

问题出现在CompanyManager类,根据迪米特法则,只与直接的朋友发生通信。

而SubEmployee类并不是CompanyManager类的直接朋友(以局部变量出现的耦合不属于直接朋友),从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,这样设计显然是增加了不必要的耦合。

按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合。修改后的代码如下

typescript 复制代码
// 总公司员工
class Employee {
    private String id;
 
    public String getId() {
        return id;
    }
 
    public void setId(String id) {
        this.id = id;
    }
}
 
// 分公司员工
class SubEmployee {
    private String id;
 
    public String getId() {
        return id;
    }
 
    public void setId(String id) {
        this.id = id;
    }
}
 
// 子公司管理
class SubCompanyManager {
    public List<SubEmployee> getAllEmployee() {
        List<SubEmployee> list = new ArrayList<>();
        for (int i = 1; i < 5; i++) {
            SubEmployee subEmployee = new SubEmployee();
            // 给分公司人员顺序分配一个ID
            subEmployee.setId("分公司" + i);
            list.add(subEmployee);
        }
        return list;
    }
 
    public void printSubCompany() {
        List<SubEmployee> subEmployeeList = this.getAllEmployee();
        for (SubEmployee subEmployee : subEmployeeList) {
            System.out.println(subEmployee.getId());
        }
    }
}
 
// 总公司管理
class CompanyManager {
    public List<Employee> getAllEmployee() {
        List<Employee> list = new ArrayList<>();
        for (int i = 1; i < 3; i++) {
            Employee employee = new Employee();
            // 给总公司人员顺序分配一个ID
            employee.setId("总公司" + i);
            list.add(employee);
        }
        return list;
    }
 
    public void printAllEmployee(SubCompanyManager subCompanyManager) {
        // 分公司员工
        subCompanyManager.printSubCompany();
 
        // 总公司员工
        List<Employee> employeeList = getAllEmployee();
        for (Employee employee : employeeList) {
            System.out.println(employee.getId());
        }
    }
}
 
public class Client {
    public static void main(String[] args) {
        CompanyManager companyManager = new CompanyManager();
        companyManager.printAllEmployee(new SubCompanyManager());
    }
}

修改后,为分公司增加了打印人员ID的方法,总公司直接调用来打印,从而避免了与分公司的员工发生耦合。

06.迪米特原则案例2

上体育课,我们经常有这样一个场景:体育老师上课前要体育委员确认一下全班女生到了多少位,也就是体育委员清点女生的人数。

java 复制代码
public class Teacher{
  //老师对体育委员发一个命令,让其清点女生人数
  public void command(GroupLeader groupLeader){
     List<Girl> 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());
  }
}

publci class Girl{
}

public class Client{
   public static void main(Strings[] args){
      Teacher teacher = new Teacher();
      //老师给体育委员发清点女生人数的命令
      teacher.command(new GroupLeader());
   }
}

我们再回头看Teacher类,Teacher类只有一个朋友类GroupLeader,Girl类不是朋友类,但是Teacher与Girl类通信了,这就破坏了Teacher类的健壮性,Teacher类的方法竟然与一个不是自己的朋友类Girl类通信,这是不允许的,严重违反了迪米特原则。

java 复制代码
public class Teacher{
  //老师对体育委员发一个命令,让其清点女生人数
  public void command(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("女生人数是:"+listGirls.size());
  }
}

public class Client{
   public static void main(Strings[] args){
     //产生女生群体
     List<Girl> listGirls = new ArrayList<Girl>();
     //初始化女生
     for(int i=0;i<20;i++){
       listGirls.add(new Girl());
     }
      Teacher teacher = new Teacher();
      //老师给体育委员发清点女生人数的命令
      teacher.command(new GroupLeader(listGirls));
   }
}

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

07.迪米特原则思考

7.1 迪米特原则要点

  1. 降低耦合性:迪米特原则鼓励将关注点分离,使得类之间的依赖关系尽可能松散。一个类应该尽量减少对其他类的直接依赖,而是通过中间类或接口进行通信。
  2. 提高模块独立性:迪米特原则鼓励将系统划分为独立的模块,每个模块只与其直接的朋友(直接依赖的类)进行通信。使得模块更加独立,修改一个模块不会对其他模块造成影响,提高了系统的可扩展性和可重用性。
  3. 保护隐私信息:迪米特原则要求一个对象只与其直接的朋友进行通信,不要暴露过多的内部信息给外部对象。这样可以保护对象的隐私信息,减少不必要的依赖和耦合。
  4. 提高代码的可维护性:通过遵循迪米特原则,代码的结构更加清晰,类之间的关系更加简单明了。这使得代码更易于理解、调试和修改,提高了代码的可维护性。

不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的类就会比较少。

7.2 迪米特原则优缺点

优点

  1. 降低类之间的耦合度,提高了模块的相对独立性
  2. 耦合度降低,从而提高了类的可重用率和系统的扩展性

缺点

  1. 过度使用迪米特原则,会产生大量的中介类,导致系统的复杂度提高。在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。

7.3 迪米特设计体现

广义的迪米特法则在类的设计上的体现:

  • 优先考虑将一个类设置成不变类。
  • 尽量降低一个类的访问权限。
  • 谨慎使用Serializable。
  • 尽量降低成员的访问权限。

7.4 思考迪米特原则场景

有几个设计模式在实现中使用了迪米特原则,包括:

  1. 外观模式(Facade Pattern):通过提供一个简化的接口,将复杂子系统的接口与客户端解耦,符合迪米特原则的要求,使得客户端不需要了解子系统的内部细节。
  2. 中介者模式(Mediator Pattern):通过引入一个中介者对象,将多个对象之间的通信集中处理,减少对象之间的直接交互,符合迪米特原则的要求。
  3. 迭代器模式(Iterator Pattern):通过提供一个统一的迭代接口,隐藏集合对象的内部结构,使得客户端不需要了解集合的具体实现细节,符合迪米特原则的要求。

这些设计模式都通过减少对象之间的直接依赖关系,降低耦合度,提高系统的可维护性和灵活性,从而符合迪米特原则的设计原则。

08.迪米特原则的总结

  1. 迪米特问题思考:什么是迪米特原则?什么是"高内聚、松耦合"?如何利用迪米特法则来实现"高内聚、松耦合"?
  2. 为何要用迪米特原则:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。这个时候要降低类之间耦合!
  3. 如何理解迪米特原则:是一种设计原则,指导软件模块之间的松耦合,即一个对象应该尽可能少地了解其他对象的内部细节。
  4. 如何理解内聚和耦合关系:"高内聚"有助于"松耦合",同理,"低内聚"也会导致"紧耦合"。
  5. 什么叫做高内聚:指相近的功能应该放到同一个类中,不相近的功能不要放到同一类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中。
  6. 什么叫做松耦合:类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动也不会或者很少导致依赖类的代码改动。
  7. 迪米特原则核心思想:类之间的解耦合,尽量做到弱耦合,耦合程度越低就说明类复用率才能提高。
  8. 迪米特原则要点:1.降低耦合性;2.提高模块独立性;3.保护隐私信息;4.提高代码可维护性。
  9. 迪米特原则的缺点:过度使用迪米特原则,会产生大量的中介类,导致系统的复杂度提高。在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
  10. 思考迪米特原则场景有哪些:比如外观模式通过简化接口使复杂子系统和客户端解耦合;迭代器模式通过迭代接口隐藏集合对象内部结构让客户端不需要了解具体细节就可以遍历。他们都符合迪米特原则。

09.更多内容推荐

模块 描述 备注
GitHub 多个YC系列开源项目,包含Android组件库,以及多个案例 GitHub
博客汇总 汇聚Java,Android,C/C++,网络协议,算法,编程总结等 YCBlogs
设计模式 六大设计原则,23种设计模式,设计模式案例,面向对象思想 设计模式
Java进阶 数据设计和原理,面向对象核心思想,IO,异常,线程和并发,JVM Java高级
网络协议 网络实际案例,网络原理和分层,Https,网络请求,故障排查 网络协议
计算机原理 计算机组成结构,框架,存储器,CPU设计,内存设计,指令编程原理,异常处理机制,IO操作和原理 计算机基础
学习C编程 C语言入门级别系统全面的学习教程,学习三到四个综合案例 C编程
C++编程 C++语言入门级别系统全面的教学教程,并发编程,核心原理 C++编程
算法实践 专栏,数组,链表,栈,队列,树,哈希,递归,查找,排序等 Leetcode
Android 基础入门,开源库解读,性能优化,Framework,方案设计 Android
相关推荐
码农的天塌了9 小时前
Java 设计模式之享元模式(Flyweight Pattern)
java·设计模式·享元模式
11在上班10 小时前
剖析initData在水合中的设计哲学
前端·设计模式
找了一圈尾巴10 小时前
设计模式(创建型)-单例模式
单例模式·设计模式
用户861782773651810 小时前
责任链
java·后端·设计模式
hope_wisdom11 小时前
实战设计模式之解释器模式
设计模式·解释器模式·软件工程·软件构建·架构设计
开发者工具分享1 天前
微服务架构中10个常用的设计模式
微服务·设计模式·架构
colin1 天前
前端场景中的设计模式
设计模式
Cutey9161 天前
前端如何实现菜单的权限控制(RBAC)
前端·javascript·设计模式
程序视点1 天前
Java中JDK里用到了哪些设计模式?让面试官眼前一亮!
java·设计模式