设计模式-迪米特法则

迪米特法则

迪米特法则 (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,然后操作 Money
              int 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 的直接朋友是 Wallet
          private 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),其目的就是暴露数据,这时获取其内部数据是正常的。迪米特法则更多地应用于行为丰富的对象。

总结:

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

相关推荐
AI人工智能+电脑小能手11 小时前
大白话说Java设计模式-34-命令模式(业务实战篇)
java·spring·设计模式·命令模式·异步任务·撤销重做·事务封装
用户2022522150613 小时前
字节发布"豆包工作",我从AI PM角度看到的不是功能,是上下文
设计模式
AI人工智能+电脑小能手14 小时前
大白话说Java设计模式-37-中介者模式(源码剖析篇)
java·设计模式·线程池·中介者模式·源码分析·executor·applicationeventmulticaster
电子科技圈16 小时前
IAR与东软睿驰达成战略合作,强化软件开发效率与生态协作
设计模式·开源·软件工程·软件构建·代码规范·设计规范·代码复审
吴声子夜歌16 小时前
Java面试——设计模式(四)
java·设计模式·面试
鲨鱼辣钊17 小时前
Github Copilot 研发效能提升实战指南
log4j·github·copilot
吴声子夜歌1 天前
Java面试——设计模式(一)
java·设计模式·面试
吴声子夜歌2 天前
Java面试——设计模式(二)
java·设计模式·面试
Larcher2 天前
SDD 项目开发步骤与提示词
人工智能·设计模式·架构
阿无,2 天前
设计模式之责任链模式
设计模式·责任链模式