07.迪米特原则介绍

简介: 本文详细介绍了迪米特原则(Law of Demeter),包括其定义、核心思想及实际应用。迪米特原则强调类之间应保持低耦合,通过限制对象间的直接交互,减少依赖关系,从而提高代码的可维护性和复用性。文章通过案例分析(如集团公司员工管理和体育课场景)展示了如何遵循该原则优化设计,避免不必要的类依赖。同时探讨了“高内聚、松耦合”的概念及其重要性,并总结了迪米特原则的优点与潜在缺点。最后列举了外观模式、中介者模式等符合迪米特原则的设计模式,帮助读者深入理解其应用场景。

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。先来看一下违反迪米特法则的设计。

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

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

// 总公司员工
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

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

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类通信,这是不允许的,严重违反了迪米特原则。

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
目录
相关文章
|
6月前
|
存储 缓存 安全
Java字符串缓冲区
字符串缓冲区是用于处理可变字符串的容器,Java中提供了`StringBuffer`和`StringBuilder`两种实现。由于`String`类不可变,当需要频繁修改字符串时,使用缓冲区更高效。`StringBuffer`是一个线程安全的容器,支持动态扩展、任意类型数据转为字符串存储,并提供多种操作方法(如`append`、`insert`、`delete`等)。通过这些方法,可以方便地对字符串进行添加、插入、删除等操作,最终将结果转换为字符串。示例代码展示了如何创建缓冲区对象并调用相关方法完成字符串操作。
140 13
|
6月前
|
数据可视化 固态存储 图形学
解锁3D创作新姿势!Autodesk 3ds Max 2022中文版安装教程(附官方下载渠道)
Autodesk 3ds Max 2022 是一款专业三维建模、动画和渲染软件,广泛应用于影视、游戏、建筑等领域。其特点包括智能建模工具、高效Arnold渲染引擎、跨平台协作及多语言支持。安装需满足Win10/11系统、i5以上处理器、8GB内存等要求。正版安装流程包括下载官方程序、配置组件、激活许可证并验证功能。常见问题如安装失败、中文乱码等提供了解决方案。扩展学习资源推荐Forest Pack、V-Ray等插件,助力用户深入掌握软件功能。
1175 24
|
6月前
|
存储 缓存 安全
Java 字符串详解
本文介绍了 Java 中的三种字符串类型:String、StringBuffer 和 StringBuilder,详细讲解了它们的区别与使用场景。String 是不可变的字符串常量,线程安全但操作效率较低;StringBuffer 是可变的字符串缓冲区,线程安全但性能稍逊;StringBuilder 同样是可变的字符串缓冲区,但非线程安全,性能更高。文章还列举了三者的常用方法,并总结了它们在不同环境下的适用情况及执行速度对比。
165 17
|
9月前
|
人工智能 算法 搜索推荐
《智育新篇:点亮学生人工智能伦理与社会责任之光》
在人工智能迅猛发展的时代,其应用已渗透到生活的方方面面,深刻改变着我们的生活模式与社会架构。然而,随之而来的伦理和社会责任问题也日益凸显。教育者需在课堂中融入伦理思辨,通过具体案例引导学生理解AI的伦理边界,如人脸识别技术的应用与隐私保护、智能机器人决策中的道德困境等。同时,培养学生的社会责任意识,使其认识到AI应服务于社会福祉,避免因商业利益导致资源分配不均。学校应搭建多元实践平台,鼓励学生参与公益项目和政策讨论,增强其对社会责任的情感认同。在全球化背景下,拓展国际视野,深化跨文化交流,共同应对AI带来的全球性挑战。
342 25
|
5月前
|
设计模式 网络协议 Java
09.接口vs抽象类比较
本文详细对比了接口与抽象类的区别及应用场景,涵盖两者的基本概念、特性以及设计思想。通过具体案例分析,如日志记录和过滤器功能,阐明抽象类适用于代码复用(is-a关系),而接口侧重解耦和行为定义(has-a关系)。此外,还探讨了如何在不支持接口或抽象类的语言中模拟其实现,并总结了选择两者的判断标准。文章结合实际开发场景,提供了清晰的指导,帮助开发者更好地理解与应用这两种核心面向对象概念。
258 26
|
6月前
|
域名解析 存储 缓存
深入学习 DNS 域名解析
在平时工作中相信大家都离不开 DNS 解析,因为 DNS 解析是互联网访问的第一步,无论是使用笔记本浏览器访问网络还是打开手机APP的时候,访问网络资源的第一步必然要经过DNS解析流程。
|
6月前
|
人工智能 JSON Java
Sring.ai生成图片的功能---OpenAiImageClient
随着大模型的升级迭代,现在越来越多的人都开始接入API接口了,尤其是JAVA的同学们,上一篇文章介绍了,从零搭建一个环境,用于调用openai的key,进行访问AI接口,进行一些对话的功能,本篇文章主要介绍生成图片的接口。希望可以帮助到正在学习spring.ai的同学一些参考。
234 2
Sring.ai生成图片的功能---OpenAiImageClient
|
6月前
|
存储 设计模式 Java
重学Java基础篇—ThreadLocal深度解析与最佳实践
ThreadLocal 是一种实现线程隔离的机制,为每个线程创建独立变量副本,适用于数据库连接管理、用户会话信息存储等场景。
218 5
|
6月前
|
设计模式 Java API
05.接口隔离原则介绍
接口隔离原则(ISP)是SOLID原则之一,强调客户端不应依赖于它们不需要的接口。通过将庞大而臃肿的接口拆分为更小、更具体的接口,确保每个接口只包含客户端真正需要的方法,从而提高代码的可维护性和灵活性。本文详细介绍了接口隔离原则的概念、核心思想、实现方式及案例分析,并对比了其与单一职责原则的区别。关键点包括:接口应精简、独立且可扩展,避免强迫实现不必要的方法,减少系统的耦合性。
278 19
|
6月前
|
设计模式 Java 数据库
06.依赖倒置原则介绍
依赖倒置原则是面向对象设计六大原则之一,强调高层模块不应依赖低层模块,两者应依赖于抽象。通过依赖接口或抽象类而非具体实现,降低模块耦合度,提升系统灵活性和可维护性。本文详解该原则的概念、目标、思想及其实现方式(如依赖注入),并结合多数据库操作、用户购买家电、发送消息等实际案例,深入探讨其应用与优缺点。
445 4

热门文章

最新文章