1. 概述

迪米特法则(Law of Demeter,简称LoD),又称最小知识原则,是面向对象设计中指导模块化开发的核心原则。它帮助开发者构建低耦合、高内聚的组件系统。

本文将深入探讨迪米特法则在Java中的实际应用,结合代码示例说明如何遵循这一原则。

2. 理解迪米特法则

迪米特法则作为面向对象编程的重要设计指南,核心思想是:对象应避免直接访问其他对象的内部数据和方法,转而只与"直接朋友"交互。

该法则最早由Karl J. Lieberherr等人在论文中提出,其形式化定义如下:

对于任意类C及其方法M,M发送消息的所有对象必须属于以下类别的实例:

  • M的参数类(包括C本身)
  • C的实例变量类

(由M创建的对象、M调用的方法/函数创建的对象、以及全局变量中的对象均视为M的参数)

简单来说,类C的方法X只能调用以下类型的方法:

  • 类C自身的方法
  • 方法X创建的对象的方法
  • 作为参数传递给X的对象的方法
  • C的实例变量持有的对象的方法
  • 静态字段的方法

这五点构成了迪米特法则的核心内容。

3. Java中的迪米特法则示例

上文将迪米特法则总结为五项规则,下面通过代码示例逐一说明。

3.1. 第一条规则

方法X应只调用类C自身的方法

class Greetings {
    
    String generalGreeting() {
        return "Welcome" + world();
    }
    String world() {
        return "Hello World";
    }
}

这里generalGreeting()调用了同类的world()方法,完全符合法则要求。

3.2. 第二条规则

方法X应只调用自己创建的对象的方法

String getHelloBrazil() {
    HelloCountries helloCountries = new HelloCountries();
    return helloCountries.helloBrazil();
}

getHelloBrazil()方法创建了HelloCountries对象并调用其方法,符合第二条规则。

3.3. 第三条规则

方法X应只调用作为参数传入的对象的方法

String getHelloIndia(HelloCountries helloCountries) {
    return helloCountries.helloIndia();
}

getHelloIndia()直接操作传入的参数对象,遵循了第三条规则。

3.4. 第四条规则

方法X应只调用类C实例变量持有的对象的方法

// ... 
HelloCountries helloCountries = new HelloCountries();
  
String getHelloJapan() {
    return helloCountries.helloJapan();
}
// ...

getHelloJapan()调用的是类的成员变量helloCountries的方法,符合第四条规则。

3.5. 第五条规则

方法X可以调用类C静态字段的方法

// ...
static HelloCountries helloCountriesStatic = new HelloCountries();
    
String getHellStaticWorld() {
    return helloCountriesStatic.helloStaticWorld();
}
// ...

getHellStaticWorld()调用类中静态对象的方法,符合第五条规则。

4. 违反迪米特法则的案例

下面分析一个违反迪米特法则的典型场景及修复方案。

4.1. 场景搭建

首先定义Employee类:

class Employee {
  
    private Department department = new Deparment();
  
    public Department getDepartment() {
        return department;
    }
}

Employee包含Department对象并提供访问方法。

接着定义Department类:

class Department {
    private Manager manager = new Manager();
  
    public Manager getManager() {
        return manager; 
    }
}

Manager类包含审批费用的方法:

class Manager {
    public void approveExpense(Expenses expenses) {
        System.out.println("Total amounts approved" + expenses.total())
    }
}

最后是Expenses类:

class Expenses {
    
    private double total;
    private double tax;
    
    public Expenses(double total, double tax) {
        this.total = total;
        this.tax = tax;
    }
    
    public double total() {
        return total + tax;
    }
}

4.2. 问题代码与修复

❌ 违反法则的调用方式

Expenses expenses = new Expenses(100, 10); 
Employee employee = new Employee();
employee.getDepartment().getManager().approveExpense(expenses);

这段链式调用严重违反迪米特法则,导致类之间高度耦合。

✅ 修复方案:将Manager作为Employee的成员变量,通过构造函数注入:

// ...
private Manager manager;
Employee(Manager manager) {
    this.manager = manager;
}
    
void submitExpense(Expenses expenses) {
    manager.approveExpense(expenses);
}
// ...

修复后的调用方式:

Manager mgr = new Manager();
Employee emp = new Employee(mgr);
emp.submitExpense(expenses);

这种实现降低了类间耦合,符合迪米特法则的要求。

5. 迪米特法则的例外情况

虽然链式调用通常违反迪米特法则,但存在以下例外:

  1. 构建器模式(Builder Pattern)
    ⚠️ 当构建器是本地创建时,链式调用不违反法则(符合第二条规则)。

  2. 流式API(Fluent APIs)
    ✅ 链式调用在本地创建的对象上合法
    ❌ 在非本地对象或返回不同对象时违反法则

  3. 数据结构操作
    ✅ 本地创建/修改/访问数据结构不违反法则
    ⚠️ 当调用从数据结构获取对象的方法时可能违反法则

6. 总结

本文系统介绍了迪米特法则的核心思想及其在Java中的实现方式。通过分析五项规则和典型场景,我们理解了如何通过限制对象交互范围来降低系统耦合度。

迪米特法则的本质是:只与直接的朋友通信,不与陌生人说话。

完整示例代码可在GitHub仓库获取。


原始标题:Law of Demeter in Java