当前位置: 首页 > news >正文

设计模式-迪米特法则

迪米特法则

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

核心思想:一个对象应该对其他对象有尽可能少的了解。

更具体地说,它规定了一个对象 O 中的一个方法 M 应该只调用以下类型对象的方法:

  1. 对象 O 本身 (this/self)。

  2. 作为方法 M 的参数传递进来的对象。

  3. 在方法 M 内部创建的对象 (局部对象)。

  4. 对象 O 的直接成员/组件对象 (实例变量/属性)。

  5. 全局对象 (但应谨慎使用,通常不推荐)。

简单来说,就是“只和你的直接朋友交谈,不要和朋友的朋友交谈”。

为什么叫“迪米特法则”?

这个名字来源于 1987 年在美国东北大学 (Northeastern University) 进行的一个名为 "Demeter Project" 的项目。该项目的目标是开发一种更容易维护和演化的面向对象系统。

目的和好处:

遵循迪米特法则的主要目的是降低类之间的耦合度 (Coupling),从而带来以下好处:

  1. 提高模块的独立性:当一个模块的内部实现发生改变时,由于它只与直接相关的模块交互,因此对其他模块的影响会降到最低。

  2. 增强系统的可维护性:修改一个类时,不需要关心太多其他类的内部细节,减少了连锁反应的风险。

  3. 提高代码的可复用性:低耦合的模块更容易被复用到其他系统中。

  4. 降低复杂性:每个类只需要关注与自己直接相关的交互,使得系统结构更清晰。

  5. 更容易进行单元测试:由于依赖关系简单,更容易对模块进行隔离测试。

违反迪米特法则的常见表现 (坏味道):

当你在代码中看到一长串的点号调用,例如 object.getA().getB().getC().doSomething(),这通常是违反迪米特法则的信号。这被称为“链式调用”或“消息链 (Message Chains)”。

这种写法的问题在于:

  • 当前对象不仅依赖于 object,还间接依赖于 A、B、C 的内部结构。

  • 如果 A、B 或 C 的任何一个类的接口发生改变(比如 getB() 方法没了,或者返回类型变了),当前对象的代码也可能需要修改,即使它本身并不直接关心 B 或 C 的具体实现。

如何遵循迪米特法则?

当发现违反迪米特法则的情况时,可以考虑以下重构方法:

  1. 封装和委托 (Encapsulation and Delegation):

    • 如果对象 O 需要调用 object.getA().getB().doSomething(),可以考虑在 object 类中添加一个新方法,比如 doSomethingRelatedToObjectB(),这个新方法内部去处理与 A 和 B 的交互,然后 O 只需要调用 object.doSomethingRelatedToObjectB() 即可。

    • 这样,object 封装了与 A 和 B 的交互细节,O 只需要与它的直接朋友 object 对话。

    例子:

    • 不好的设计 (违反 LoD):

      class Wallet {private Money money;public Wallet(Money money) { this.money = money; }public Money getMoney() { return money; }
      }
      ​
      class Money {private int amount;public Money(int amount) { this.amount = amount; }public int getAmount() { return amount; }public void setAmount(int amount) { this.amount = amount; }
      }
      ​
      class Person {private Wallet wallet;public Person(Wallet wallet) { this.wallet = wallet; }
      ​public void pay(int amountToPay) {// 违反了 LoD:Person 通过 Wallet 拿到了 Money,然后操作 Moneyint currentAmount = wallet.getMoney().getAmount();if (currentAmount >= amountToPay) {wallet.getMoney().setAmount(currentAmount - amountToPay);System.out.println("支付成功: " + amountToPay);} else {System.out.println("余额不足");}}
      }
    • 好的设计 (遵循 LoD):

      class Wallet { // Wallet 作为 Money 的直接朋友private Money money;public Wallet(Money money) { this.money = money; }
      ​// Wallet 负责处理支付逻辑,而不是暴露 Money 对象public boolean spend(int amountToSpend) {return money.deduct(amountToSpend);}
      ​public int getCurrentBalance() {return money.getAmount();}
      }
      ​
      class Money {private int amount;public Money(int amount) { this.amount = amount; }public int getAmount() { return amount; }
      ​public boolean deduct(int amountToDeduct) { // Money 自己负责扣款if (this.amount >= amountToDeduct) {this.amount -= amountToDeduct;return true;}return false;}
      }
      ​
      class Person { // Person 的直接朋友是 Walletprivate Wallet wallet;public Person(Wallet wallet) { this.wallet = wallet; }
      ​public void pay(int amountToPay) {// Person 只和它的直接朋友 Wallet 交互if (wallet.spend(amountToPay)) {System.out.println("支付成功: " + amountToPay);} else {System.out.println("余额不足,当前余额: " + wallet.getCurrentBalance());}}
      }

      在这个改进后的例子中,Person 不再需要知道 Wallet 内部是如何管理 Money 的,也不需要直接操作 Money 对象。Person 只与 Wallet 交互,告诉它要支付多少钱。Wallet 负责具体的支付逻辑,它与它的直接朋友 Money 交互。

  2. 移动方法 (Move Method):

    • 如果一个方法过多地使用了另一个类的数据和方法,可以考虑将这个方法移动到那个类中。

需要注意的平衡:

  • 过度应用可能导致问题:如果为了严格遵守迪米特法则而创建大量仅仅是进行简单委托的“包装方法 (Wrapper Methods)”,可能会导致类的接口膨胀,反而增加系统的复杂性。

  • Getter 方法本身不一定违反 LoD:object.getData() 本身不违反迪米特法则,因为 Data 对象是 object 的一个组件(或者是通过参数传入的,或者是局部创建的)。问题在于你如何使用这个返回的 Data 对象。如果你只是读取 Data 的状态(比如 data.getValue()),通常是可以接受的。但如果你接着调用 data.getAnotherObject().doSomething(),那就可能违反了。

  • 数据结构 (Data Structures) vs. 对象 (Objects):对于纯粹的数据结构(例如,DTO - Data Transfer Object),其目的就是暴露数据,这时获取其内部数据是正常的。迪米特法则更多地应用于行为丰富的对象。

总结:

迪米特法则是指导我们设计低耦合、高内聚系统的一个重要原则。它的核心思想是限制对象之间的知识,让每个对象只与它的直接朋友交互。通过封装和委托,我们可以有效地应用迪米特法则,从而创建出更易于维护、扩展和理解的软件系统。但同时也要注意避免过度设计,找到一个合适的平衡点。

相关文章:

  • 3D视觉重构工业智造:解码迁移科技如何用“硬核之眼“重塑生产节拍
  • Doris查询Hive数据:实现高效跨数据源分析的实践指南
  • hive 3集成Iceberg 1.7中的Java版本问题
  • Duix.HeyGem:以“离线+开源”重构数字人创作生态
  • 大数据学习(128)-数据分析实例
  • 【网络安全】漏洞分析:阿帕奇漏洞学习
  • 大数据学习(129)-Hive数据分析
  • 【Web应用】若依框架:基础篇14 源码阅读-后端代码分析-课程管理模块前后端代码分析
  • 设计模式杂谈-模板设计模式
  • 容器化实施:Docker容器构建与优化深度剖析
  • (2025)Windows修改JupyterNotebook的字体,使用JetBrains Mono
  • Python爬虫(48)基于Scrapy-Redis与深度强化学习的智能分布式爬虫架构设计与实践
  • 内网穿透:打破网络限制的利器!深入探索和简单实现方案
  • 解决pycharm同一个文件夹下from *** import***仍显示No module named
  • 在VSCode中开发一个uni-app项目
  • python打卡day44@浙大疏锦行
  • 《PyTorch:开启深度学习新世界的魔法之门》
  • [Python] python信号处理绘制信号频谱
  • Transformer核心原理
  • SIFT 算法原理详解
  • 二维码导航网站源码/十大搜索引擎神器
  • 做设计找图片的网站/网站新站整站排名
  • 做毕业设计一个网站的数据来源/seo搜狗
  • 网站优化怎么做外链/做外贸网站的公司
  • 想做个自己的网站/广州网站建设系统
  • 英语网站建设/百度推广电话客服24小时