Java中的迪米特法则

2025/04/20

1. 概述

迪米特法则(LoD),即最少知识原则,为模块化软件开发提供了面向对象的设计原则,它有助于构建相互依赖性较低且松散耦合的组件。

在本教程中,我们将深入研究迪米特法则及其在Java中的应用。

2. 理解迪米特法则

迪米特法则是面向对象编程中的几条设计准则之一,它建议对象应避免访问其他对象的内部数据和方法。相反,对象应该只与其直接依赖的对象进行交互。

这一概念最初是由Karl J.Lieberherr等人在一篇论文中提出的,论文指出:

对于所有类C以及附加到C的所有方法M,M向其发送消息的所有对象都必须是与以下类关联的类的实例:

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

(由M创建的对象,或由M调用的函数或方法创建的对象,以及全局变量中的对象被视为M的参数。)

简而言之,该定律规定类C的方法X只能调用以下方法:

  • C类本身
  • X创建的对象
  • 作为参数传递给X的对象
  • C的实例变量中保存的对象
  • 静态字段

该定律概括为五点。

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

在上一节中,我们将迪米特法则提炼为五条关键规则,让我们用一些示例代码来说明这些要点。

3.1 第一条规则

第一条规则规定,类C的方法X应该只调用C的方法

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

这里,generalGreeting()方法调用了同一个类中的world()方法。这符合规律,因为它们属于同一个类。

3.2 第二条规则

类C的方法X应该只调用由X创建的对象的方法

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

在上面的代码中,我们创建了一个HelloCountries对象,并在其上调用了helloBrazil()方法,这符合getHelloBrazil()方法本身创建对象的规律。

3.3 第三条规则

此外,第三条规则规定方法X应该只调用作为参数传递给X的对象

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

这里,我们将一个HelloCountries对象作为参数传递给getHelloIndia(),将对象作为参数传递使得该方法与对象更加亲近,并且可以在不违反迪米特法则的情况下调用其方法。

3.4 第四条规则

类C的方法X应该只调用C的实例变量中保存的对象的方法

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

在上面的代码中,我们在Greetings类中创建了一个实例变量“helloCountries”。然后,我们在getHelloJapan()方法中对该实例变量调用了helloJapan()方法,这符合第四条规则。

3.5 第五条规则

最后,类C的方法X可以调用在C中创建的静态字段的方法

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

这里,该方法在类中创建的静态对象上调用helloStaticWorld()方法。

4. 违反迪米特法则

让我们检查一些违反迪米特法则的示例代码并寻找可能的修复方法。

4.1 设置

我们首先定义一个Employee类:

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

Employee类包含一个对象引用成员变量并为其提供访问器方法。

接下来,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 使用

这些类通过它们之间的关系表现出紧密耦合,我们将通过让Manager批准Expenses来演示迪米特法则的违反:

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

上面的代码中,我们进行了链式调用,这违反了迪米特法则。类之间紧密耦合,无法独立运行。

让我们通过将Manager类作为Employee中的实例变量来修复此违规,它将通过Employee构造函数传入。然后,我们将在Employee类中创建submitExpense()方法,并在其中调用Manager的approveExpense()方法:

// ...
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. 迪米特法则的例外

链式调用通常意味着违反迪米特法则,但也有例外。例如,如果构建器模式是在本地实例化的,则它并不违反迪米特法则。其中一条规则规定:“类C的方法X应该只调用由X创建的对象的方法”。

此外,流式API中存在链式调用,如果链式调用针对的是本地创建的对象,则流式API不会违反迪米特法则。但是,当链式调用针对的是非本地实例化的对象或返回不同的对象时,就会违反迪米特法则。

此外,在处理数据结构时,有些情况下我们可能会违反迪米特法则。典型的数据结构用法,例如实例化、修改和本地访问,并不违反迪米特法则。但当我们调用从数据结构中获取的对象的方法时,就可能违反迪米特法则。

6. 总结

在本文中,我们学习了迪米特法则的应用以及如何在面向对象代码中遵循它。此外,我们还通过代码示例深入探讨了每条法则,迪米特法则通过将对象交互限制在直接依赖关系中来促进松散耦合。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章