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. 迪米特法则的例外情况
虽然链式调用通常违反迪米特法则,但存在以下例外:
构建器模式(Builder Pattern)
⚠️ 当构建器是本地创建时,链式调用不违反法则(符合第二条规则)。流式API(Fluent APIs)
✅ 链式调用在本地创建的对象上合法
❌ 在非本地对象或返回不同对象时违反法则数据结构操作
✅ 本地创建/修改/访问数据结构不违反法则
⚠️ 当调用从数据结构获取对象的方法时可能违反法则
6. 总结
本文系统介绍了迪米特法则的核心思想及其在Java中的实现方式。通过分析五项规则和典型场景,我们理解了如何通过限制对象交互范围来降低系统耦合度。
迪米特法则的本质是:只与直接的朋友通信,不与陌生人说话。
完整示例代码可在GitHub仓库获取。