迪米特法则(Law of Demeter, LoD)
别名:最少知识原则(Least Knowledge Principle)
核心思想:一个对象应尽可能少地与其他对象发生交互,只与直接的朋友(成员变量、方法参数、方法返回值中的对象)通信,避免依赖间接的类。
原理详解
直接朋友的定义:
- 当前对象的成员变量。
- 当前对象方法的参数。
- 当前对象方法的返回值。
- 当前对象方法中创建的对象(不推荐,但允许)。
禁止链式调用:
避免出现a.getB().getC().doSomething()
的调用形式,这种“火车残骸式”代码会增加耦合性。目标:
- 降低耦合:减少模块间的依赖,提升代码可维护性。
- 提高封装性:隐藏内部实现细节,仅暴露必要接口。
应用案例
场景1:订单系统获取用户配送地址
错误设计(违反迪米特法则)
public class User {
private Order order;
public Order getOrder() { return order; }
}
public class Order {
private Address shippingAddress;
public Address getShippingAddress() { return shippingAddress; }
}
public class Address {
private String city;
public String getCity() { return city; }
}
// 客户端代码:链式调用(直接访问深层对象)
User user = new User();
String city = user.getOrder().getShippingAddress().getCity();
问题:
- 客户端需要了解
User
、Order
、Address
的内部结构。 - 修改
Order
或Address
的结构会影响客户端代码。
正确设计(遵循迪米特法则)
public class User {
private Order order;
public String getShippingCity() {
return order.getShippingCity(); // 委托给 Order 类
}
}
public class Order {
private Address shippingAddress;
public String getShippingCity() {
return shippingAddress.getCity(); // 委托给 Address 类
}
}
public class Address {
private String city;
public String getCity() { return city; }
}
// 客户端代码:仅与直接朋友交互
User user = new User();
String city = user.getShippingCity();
优势:
- 客户端只需与
User
交互,无需了解Order
和Address
的细节。 - 修改
Order
或Address
的结构不会影响客户端代码。
场景2:文件系统目录结构遍历
错误设计(违反迪米特法则)
public class Directory {
private List<File> files;
private List<Directory> subDirectories;
public List<File> getFiles() { return files; }
public List<Directory> getSubDirectories() { return subDirectories; }
}
// 客户端代码:直接遍历深层结构
public void printAllFiles(Directory root) {
for (File file : root.getFiles()) {
System.out.println(file.getName());
}
for (Directory subDir : root.getSubDirectories()) {
printAllFiles(subDir); // 递归调用暴露内部结构
}
}
问题:
- 客户端需要了解目录的递归结构,耦合度高。
正确设计(遵循迪米特法则)
public class Directory {
private List<File> files;
private List<Directory> subDirectories;
// 封装遍历逻辑,客户端无需了解内部结构
public void traverseFiles(Consumer<File> fileConsumer) {
files.forEach(fileConsumer);
subDirectories.forEach(subDir -> subDir.traverseFiles(fileConsumer));
}
}
// 客户端代码:仅调用高层方法
Directory root = new Directory();
root.traverseFiles(file -> System.out.println(file.getName()));
优势:
- 客户端仅依赖
Directory
的traverseFiles
方法,不关心内部实现。 - 目录结构的遍历逻辑被封装,修改不影响客户端。
迪米特法则的实践意义
- 减少耦合:模块间通过接口通信,而非直接操作内部对象。
- 提升可维护性:修改一个类的内部结构时,无需调整其他模块。
- 增强可测试性:依赖越少,单元测试越容易隔离和模拟。
常见违反场景及修复
违反场景 | 修复方法 |
---|---|
链式调用(a.getB().getC() ) |
封装中间调用,提供高层接口(如 a.getC() ) |
暴露集合内部结构 | 返回不可变集合或迭代器,避免直接操作 |
方法参数传递复杂对象 | 拆分参数为基本类型或接口 |
总结
迪米特法则通过限制对象间的交互范围,推动代码向高内聚、低耦合的方向演进。其核心是封装和委托,适用于任何需要降低依赖关系的场景,尤其在大型系统或模块化架构中价值显著。