Java 迪米特法则 3 大典型误用场景:从 2 个反例到 1 个正解

Java 迪米特法则 3 大典型误用场景:从 2 个反例到 1 个正解

Java 迪米特法则实战:3个典型误用场景与重构方案

引言:为什么你的代码总是"知道太多"?

在Java开发中,我们经常遇到这样的困境:明明只是调用一个简单的方法,却需要了解整个调用链路上所有对象的内部结构。这种"过度了解"不仅增加了代码的维护成本,还让系统变得脆弱不堪。迪米特法则(Law of Demeter)正是解决这一问题的金钥匙——它要求每个对象只与"直接朋友"交谈,而不要和"陌生人"说话。

想象一下这样的场景:你需要从图书馆借一本书,图书管理员却要求你先了解这本书的印刷厂、纸张供应商和运输路线。这显然不合理!同样,在代码中,一个类也不应该了解它依赖对象的所有细节。本文将揭示Java项目中三种最常见的迪米特法则误用模式,并通过实际案例展示如何重构为优雅的解决方案。

1. 方法链式调用:层层深入的耦合陷阱

反例:深度方法调用链

// 违反迪米特法则的典型例子 public class OrderService { public void processOrder(Order order) { Customer customer = order.getCustomer(); Address address = customer.getAddress(); String city = address.getCity(); // 使用city进行业务处理... } }

这段代码看似合理,实则违反了迪米特法则。OrderService为了获取城市信息,需要依次通过OrderCustomerAddressgetCity()的调用链。这种"火车残骸"式的代码(称为"Train Wreck")存在几个严重问题:

  1. 过度暴露实现细节:上层服务需要了解下层所有对象的结构
  2. 脆弱性:中间任何一个环节的修改都会导致调用方代码变更
  3. 难以测试:需要mock整个对象链才能进行单元测试

重构方案:封装完整行为

// 遵循迪米特法则的重构版本 public class Order { public String getCustomerCity() { return customer.getAddress().getCity(); } } public class OrderService { public void processOrder(Order order) { String city = order.getCustomerCity(); // 使用city进行业务处理... } }

关键改进点

  • 将长方法调用链封装在Order内部
  • OrderService只需与直接朋友Order交互
  • 城市信息的获取逻辑变化不会影响服务层

决策流程图:何时应该重构方法链?

graph TD A[发现超过1个"."的方法调用] --> B{是否属于同一抽象层级?} B -->|是| C[可以保留] B -->|否| D[考虑封装到下层对象] D --> E[新方法命名应体现业务含义]

2. 暴露内部结构:当DTO变成了数据仓库

反例:直接操作集合元素

public class School { private List<Department> departments; public List<Department> getDepartments() { return departments; // 直接返回内部集合 } } public class ReportService { public void generateReport(School school) { for (Department dept : school.getDepartments()) { // 直接操作Department对象 if (dept.getTeacherCount() > 10) { // 业务逻辑... } } } }

这种模式的危害在于:

  1. 破坏封装性:外部可以直接修改School的内部集合
  2. 业务逻辑泄露:教师数量的判断应该属于School的职责
  3. 难以扩展:如果需要过滤条件变化,需要修改所有调用方

重构方案:提供行为而非数据

public class School { private List<Department> departments; public void processLargeDepartments(Consumer<Department> processor) { departments.stream() .filter(dept -> dept.getTeacherCount() > 10) .forEach(processor); } } public class ReportService { public void generateReport(School school) { school.processLargeDepartments(dept -> { // 只处理符合条件的部门 }); } }

改进效果

  • 内部集合结构完全隐藏
  • 过滤条件由School统一维护
  • 业务语义更加清晰("处理大型部门")

集合处理的三种正确姿势

  1. 防御性拷贝(适用于不可变数据):

    public List<Department> getDepartments() { return new ArrayList<>(departments); }
  2. 视图方法(Java 9+):

    public List<Department> getDepartmentsView() { return List.copyOf(departments); }
  3. 行为暴露(推荐方式):

    public void forEachDepartment(Consumer<Department> action) { departments.forEach(action); }

3. 服务类膨胀:当Utils成为上帝类

反例:集中式服务工具类

public class EmployeeUtils { public static void processPayroll(Employee emp) { BankAccount account = emp.getBankAccount(); TaxInfo taxInfo = emp.getTaxInfo(); // 长达数百行的处理逻辑... } public static void generateReport(Employee emp) { Department dept = emp.getDepartment(); Manager mgr = emp.getManager(); // 各种报表生成代码... } }

这种"上帝类"的问题在于:

  1. 违反单一职责原则:一个类承担过多功能
  2. 高耦合:需要了解Employee的所有细节
  3. 难以维护:任何Employee结构的变动都会影响此类

重构方案:职责分布到领域对象

public class Employee { private BankAccount account; private TaxInfo taxInfo; public PayrollResult processPayroll(PayrollCalculator calculator) { return calculator.calculate(this, account, taxInfo); } } public class PayrollCalculator { public PayrollResult calculate(Employee emp, BankAccount account, TaxInfo tax) { // 具体的薪资计算逻辑 } }

领域驱动设计应用

  • Employee作为聚合根控制主要行为
  • 具体计算逻辑委托给专门的策略类
  • 外部服务只需与Employee交互

服务类设计的黄金法则

  1. 角色接口原则:按调用者角色定义接口,而非按实现

    public interface PayrollProcessor { PayrollResult process(Employee emp); }
  2. 依赖注入:避免静态方法,提高可测试性

    public class PayrollService { private final PayrollProcessor processor; public PayrollService(PayrollProcessor processor) { this.processor = processor; } }
  3. 上下文参数化:将运行时数据通过参数传递

    public interface ReportGenerator { void generate(ReportContext context); }

4. 实战重构:从学校管理系统看迪米特应用

原始问题代码分析

public class SchoolManager { public void printAllEmployee(CollegeManager sub) { // 直接操作CollegeEmployee(陌生人) List<CollegeEmployee> list1 = sub.getAllEmployee(); for (CollegeEmployee e : list1) { System.out.println(e.getId()); } // 操作自己的Employee(朋友) List<Employee> list2 = this.getAllEmployee(); for (Employee e : list2) { System.out.println(e.getId()); } } }

违反点分析

  1. CollegeEmployee作为局部变量出现(陌生人)
  2. 学院员工的打印逻辑应该属于CollegeManager
  3. 学校总部员工打印逻辑与学院员工打印耦合

分步骤重构过程

第一步:识别直接朋友

  • Employee(成员变量)
  • CollegeManager(方法参数)

第二步:移除陌生人依赖

public class CollegeManager { public void printEmployee() { List<CollegeEmployee> list = getAllEmployee(); for (CollegeEmployee e : list) { System.out.println(e.getId()); } } }

第三步:简化主类逻辑

public class SchoolManager { public void printAllEmployee(CollegeManager sub) { sub.printEmployee(); // 委托给朋友 List<Employee> list = this.getAllEmployee(); for (Employee e : list) { System.out.println(e.getId()); } } }

最终效果对比

指标重构前重构后
类间耦合度高(3个类直接依赖)低(2个类依赖)
修改影响范围需要修改多处只需修改对应类
单元测试难度需要mock多个类只需mock直接朋友
业务语义清晰度混合关注点单一职责

5. 高级应用:迪米特法则在分层架构中的实践

分层架构中的朋友关系

在典型的三层架构中,各层的"朋友"定义如下:

  1. 表示层

    • 直接朋友:服务接口、DTO
    • 陌生人:领域对象、数据访问对象
  2. 业务层

    • 直接朋友:领域模型、仓储接口
    • 陌生人:具体技术实现(如JDBC、Redis)
  3. 数据层

    • 直接朋友:数据库连接、ORM框架
    • 陌生人:业务规则、UI组件

跨层调用的正确姿势

反模式:表示层直接访问DAO

// 在Controller中 @Controller public class UserController { @Autowired private UserDao userDao; // 违反分层原则 public void saveUser(UserForm form) { User user = convert(form); userDao.save(user); // 直接跳过了服务层 } }

正确做法:通过服务层中介

@Controller public class UserController { @Autowired private UserService userService; // 直接朋友 public void saveUser(UserForm form) { userService.registerUser(form); } } @Service public class UserService { @Autowired private UserRepository repository; public void registerUser(UserForm form) { // 业务逻辑处理... repository.save(user); } }

领域驱动设计中的迪米特应用

在DDD中,迪米特法则表现为:

  1. 聚合根封装:外部只能通过聚合根访问内部对象

    public class Order { private List<OrderItem> items; public void addItem(Product product, int quantity) { // 封装所有业务规则 if (canAddItem(product)) { items.add(new OrderItem(product, quantity)); } } }
  2. 限界上下文隔离:不同上下文通过防腐层交互

    public class InventoryService { private final InventoryClient client; public boolean isProductAvailable(ProductId id) { // 转换为外部系统理解的DTO InventoryRequest request = convert(id); return client.checkStock(request); } }
  3. 领域事件通知:通过事件而非直接方法调用

    public class Order { public void cancel() { this.status = CANCELLED; registerEvent(new OrderCancelled(this.id)); } }

6. 性能与可维护性的平衡艺术

何时可以放宽迪米特法则?

虽然迪米特法则能提高代码质量,但在以下场景可以适当放宽:

  1. DTO/VO对象:数据传输对象天然需要暴露字段

    public class UserDto { public String username; // 公开字段可以接受 public String email; }
  2. 性能关键路径:避免不必要的封装导致性能下降

    // 游戏开发中的典型例子 public class ParticleSystem { public Particle[] particles; // 直接暴露数组以提高性能 }
  3. 内部框架代码:框架底层需要直接操作对象

    // ORM框架中的实体操作 public class EntityAccessor { public Object getField(Object entity, String fieldName) { // 通过反射直接访问字段 } }

测量耦合度的三个指标

  1. 响应度(Response For Class, RFC)

    • 计算方法:类的方法集合 + 这些方法直接调用的其他方法数量
    • 健康值:RFC < 50
  2. 耦合度(Coupling Between Objects, CBO)

    • 计算方法:与当前类直接关联的类数量
    • 健康值:CBO < 10
  3. 信息流(Information Flow, IF)

    • 计算方法:Fan-in × Fan-out(传入调用×传出调用)
    • 健康值:IF < 50

示例:使用JDepend测量代码质量

jdepend.xml: <metric name="CBO" max="10" /> <metric name="RFC" max="50" /> <metric name="IF" max="50" />

7. 从理论到实践:建立迪米特编码习惯

日常开发中的自查清单

在提交代码前,问自己这几个问题:

  1. 这个类是否只与以下对象交互?

    • 自身的成员变量
    • 方法参数
    • 自己创建的对象
    • 组件注入的对象
  2. 方法中是否存在超过1个"."的调用链?

  3. 是否将集合/数组直接返回给调用方?

  4. 工具类是否知道太多领域对象的细节?

IntelliJ IDEA检测技巧

  1. 方法链检测

    • 设置 → Editor → Inspections → Java → Class metrics → Method chain
    • 设置最大链长度为1
  2. 耦合度检测

    • 使用"Analyze → Analyze Dependencies"查看类依赖
    • 使用"Diagrams → Show Dependencies"生成可视化图表
  3. 自定义Live Template

    // 快速生成符合迪米特的委托方法 public $RETURN_TYPE$ $DELEGATE_METHOD$($PARAMS$) { return this.$DELEGATE_FIELD$.$ORIGINAL_METHOD$($ARGS$); }

团队代码审查要点

  1. 重点审查

    • 超过3层的方法调用链
    • 返回集合/数组的getter方法
    • 静态工具类对领域对象的操作
  2. 审查话术示例

    • "这个服务类是否可以直接调用DAO?"
    • "这个长方法调用链能否封装到下层对象?"
    • "外部类是否需要知道这么多内部细节?"
  3. 渐进式改进策略

    • 第一阶段:消除明显的"火车残骸"代码
    • 第二阶段:重构返回集合的getter方法
    • 第三阶段:优化跨层调用
    • 第四阶段:建立领域模型间的清晰边界

8. 常见问题解答:迪米特法则的误区澄清

Q1:迪米特法则会导致大量包装方法吗?

误解:遵循迪米特会产生大量仅调用另一个方法的"包装方法"。

事实:良好的设计应该基于行为而非数据暴露。真正的问题在于:

  • 如果发现自己在写大量委托方法,说明类职责划分可能有问题
  • 应该将相关行为内聚到合适的类中,而非简单委托

示例对比

// 不好的做法:简单委托 public class OrderService { private OrderRepository repository; public Order findById(long id) { return repository.findById(id); } public void save(Order order) { repository.save(order); } // 数十个类似的委托方法... } // 好的做法:富含业务语义 public class OrderService { private OrderRepository repository; public Order checkout(Cart cart) { Order order = createOrderFromCart(cart); return repository.save(order); } public Order cancelOrder(long id) { Order order = repository.findById(id); order.cancel(); return repository.save(order); } }

Q2:如何区分"必要耦合"和"过度耦合"?

判断标准

特征必要耦合过度耦合
变更影响影响范围可控引发连锁反应
语义合理性符合领域概念关系技术实现细节泄露
测试难度容易mock需要复杂测试脚手架
文档描述能清晰说明业务关系难以用业务语言解释

示例

// 必要耦合:订单与客户的关系是领域核心 public class Order { private Customer customer; // 合理耦合 } // 过度耦合:报表服务需要了解订单项折扣计算细节 public class ReportService { public void generate(Order order) { for (Item item : order.getItems()) { Discount discount = item.getDiscount(); // 深入处理折扣细节... } } }

Q3:迪米特法则与SOLID原则的关系

互补关系

  1. 单一职责原则(SRP)

    • 迪米特帮助识别类是否承担过多职责
    • 一个类如果知道太多"陌生人",可能违反SRP
  2. 开闭原则(OCP)

    • 迪米特通过减少耦合使系统更易扩展
    • 修改被隔离在最小范围内
  3. 接口隔离原则(ISP)

    • 两者都强调最小化依赖
    • ISP关注接口粒度,迪米特关注对象交互
  4. 依赖倒置原则(DIP)

    • 迪米特是DIP的具体实现手段之一
    • 都提倡依赖于抽象

协同应用示例

// 结合SOLID和迪米特的设计 public interface OrderProcessor { // DIP void process(Order order); } public class BasicOrderProcessor implements OrderProcessor { private final PaymentGateway gateway; // 通过接口依赖 public void process(Order order) { // 仅与直接朋友交互 order.validate(); gateway.charge(order.getTotal()); order.complete(); } }

9. 工具链支持:自动化检测与重构

静态分析工具

  1. SpotBugs规则

    • LOD_LACK_OF_DEMETER:检测明显违反迪米特的代码
    • 配置示例:
      <Match> <Bug pattern="LOD_LACK_OF_DEMETER" /> <Priority value="2" /> </Match>
  2. SonarQube指标

    • java:S1201:方法链过长
    • java:S2384:公共方法返回内部数组
    • 质量阈设置:
      "rules": { "java:S1201": {"level": "CRITICAL"}, "java:S2384": {"level": "MAJOR"} }
  3. ArchUnit测试

    @ArchTest public static final ArchRule demeter_rule = ArchRuleDefinition.noClasses() .should().accessClassesThat().resideInAnyPackage("..internal..") .orShould().callMethodWhere(JavaMethod.Predicates.chainCall());

重构技术

  1. 提取方法对象

    // 重构前 public void process(Order order) { order.getCustomer().getAddress().validate(); // 复杂处理逻辑... } // 重构后 public void process(Order order) { new OrderProcessor(order).execute(); } private class OrderProcessor { private final Order order; OrderProcessor(Order order) { this.order = order; } void execute() { order.validateCustomerAddress(); // 处理逻辑... } }
  2. 引入参数对象

    // 重构前 public void register(String name, String email, String street, String city) { // 需要知道所有参数细节 } // 重构后 public void register(RegistrationForm form) { // 只需与form对象交互 }
  3. 替换委托为继承(谨慎使用):

    // 重构前 public class Stack { private ArrayList elements = new ArrayList(); public void push(Object o) { elements.add(o); } } // 重构后 public class Stack extends ArrayList { public void push(Object o) { add(o); } }

10. 真实项目案例:电商系统重构实录

重构前代码分析

场景:订单价格计算服务

public class PricingService { public BigDecimal calculatePrice(Order order) { BigDecimal total = BigDecimal.ZERO; // 违反迪米特:深入订单项和产品细节 for (OrderItem item : order.getItems()) { Product product = item.getProduct(); BigDecimal basePrice = product.getPricing().getBasePrice(); BigDecimal discount = product.getPricing().getCurrentDiscount(); total = total.add( basePrice.multiply(BigDecimal.ONE.subtract(discount)) .multiply(new BigDecimal(item.getQuantity()))); } // 违反迪米特:直接操作会员等级 MemberLevel level = order.getCustomer().getMember().getLevel(); total = total.multiply(level.getDiscountFactor()); return total; } }

主要问题

  1. 深入订单项、产品、定价、会员等多层对象
  2. 价格计算逻辑分散在各层对象中
  3. 任何底层模型修改都会影响此服务

分阶段重构过程

阶段一:封装产品价格计算

public class Product { private Pricing pricing; public BigDecimal getFinalPrice(int quantity) { return pricing.getBasePrice() .multiply(BigDecimal.ONE.subtract(pricing.getCurrentDiscount())) .multiply(new BigDecimal(quantity)); } }

阶段二:封装订单项总计

public class OrderItem { private Product product; private int quantity; public BigDecimal getSubtotal() { return product.getFinalPrice(quantity); } }

阶段三:封装会员折扣

public class Customer { private Member member; public BigDecimal applyDiscount(BigDecimal amount) { return amount.multiply(member.getLevel().getDiscountFactor()); } }

最终服务代码

public class PricingService { public BigDecimal calculatePrice(Order order) { BigDecimal subtotal = order.getItems().stream() .map(OrderItem::getSubtotal) .reduce(BigDecimal.ZERO, BigDecimal::add); return order.getCustomer().applyDiscount(subtotal); } }

重构效果评估

代码指标对比

指标重构前重构后
方法复杂度(Cyclomatic)123
类耦合度(CBO)63
代码行数(LOC)258
可测试性需要mock 5个类只需mock 2个类

业务收益

  1. 价格计算逻辑现在由各领域对象自己维护
  2. 新增折扣类型只需修改对应类,不影响服务层
  3. 单元测试更容易编写和维护
  4. 业务语义更加清晰明了

11. 设计模式中的迪米特应用

典型设计模式实现

  1. 外观模式(Facade)

    public class OrderFacade { private InventoryService inventory; private PaymentService payment; private ShippingService shipping; public OrderResult placeOrder(Order order) { inventory.checkStock(order); payment.process(order); return shipping.scheduleDelivery(order); } }

    效果:客户端只需与Facade交互,无需了解子系统细节

  2. 中介者模式(Mediator)

    public class AirTrafficControl { private List<Aircraft> aircrafts; public void requestLanding(Aircraft aircraft) { // 协调所有飞机交互 aircrafts.forEach(a -> a.adjustCourse()); aircraft.clearToLand(); } }

    效果:飞机之间不直接通信,通过控制塔中介

  3. 代理模式(Proxy)

    public class ImageProxy implements Image { private RealImage realImage; public void display() { if (realImage == null) { realImage = new RealImage(filename); } realImage.display(); } }

    效果:客户端与代理交互,不知晓真实对象细节

模式选择决策树

graph TD A[需要隐藏复杂子系统?] -->|是| B[外观模式] A -->|否| C{对象需要集中协调?} C -->|是| D[中介者模式] C -->|否| E{需要控制对象访问?} E -->|是| F[代理模式] E -->|否| G[考虑迪米特基本封装]

12. 微服务架构中的迪米特法则

服务边界的迪米特应用

  1. 服务接口设计

    • 提供粗粒度API,避免"聊天式"交互
    • 示例:
      // 不符合迪米特 @GetMapping("/users/{id}/address/street") // 符合迪米特 @GetMapping("/users/{id}/profile")
  2. DTO设计原则

    • 包含完整的上下文信息
    • 避免嵌套过深的结构
    • 示例:
      // 不符合迪米特 public class OrderDto { public CustomerDto customer; // 嵌套过深 } // 符合迪米特 public class OrderDto { public String customerName; public String shippingAddress; }
  3. 事件驱动交互

    // 订单服务发布事件 public class OrderService { private EventPublisher publisher; public void completeOrder(Order order) { publisher.publish(new OrderCompleted( order.getId(), order.getTotalAmount() )); } } // 物流服务订阅事件 public class ShippingListener { @EventListener public void onOrderCompleted(OrderCompleted event) { // 只需处理事件数据,不了解订单内部 } }

服务网格中的实践

  1. Sidecar模式

    • 每个服务只与自己的sidecar交互
    • sidecar处理与其他服务的通信细节
  2. 服务契约

    • 定义清晰的接口规范
    • 使用Protobuf/OpenAPI等IDL语言
  3. 混沌工程验证

    • 随机断开服务间连接
    • 验证是否遵循了最小知识原则

13. 测试策略:如何验证迪米特合规性

单元测试验证

  1. Mock验证法

    @Test public void shouldOnlyDependOnDirectFriends() { // 设置 Order order = mock(Order.class); Customer customer = mock(Customer.class); when(order.getCustomer()).thenReturn(customer); // 执行 service.process(order); // 验证 verify(order).validate(); verifyZeroInteractions(customer); // 不应与Customer交互 }
  2. 反射检测法

    @Test public void checkNoForbiddenDependencies() { Set<Class<?>> allowed = Set.of(Order.class, Logger.class); for (Field field : service.getClass().getDeclaredFields()) { assertTrue(allowed.contains(field.getType())); } }

集成测试策略

  1. 组件边界测试

    @SpringBootTest public class ComponentBoundaryTest { @Autowired private ApplicationContext context; @Test public void checkLayerDependencies() { for (String beanName : context.getBeanDefinitionNames()) { Object bean = context.getBean(beanName); // 验证Controller不直接依赖Repository等规则 } } }
  2. 架构测试

    @Test public void enforceDemeterInLayers() { JavaClasses classes = new ClassFileImporter() .importPackages("com.myapp"); ArchRule rule = layeredArchitecture() .layer("Controller").definedBy("..web..") .layer("Service").definedBy("..service..") .layer("Repository").definedBy("..dao..") .whereLayer("Controller").mayNotBeAccessedByAnyLayer() .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller") .whereLayer("Repository").mayOnlyBeAccessedByLayers("Service"); rule.check(classes); }

14. 反模式警示:这些"优化"其实在破坏迪米特

常见错误实践

  1. 过度封装

    // 反面例子:无意义的封装 public class Order { private Customer customer; public String getCustomerName() { return customer.getName(); } public String getCustomerEmail() { return customer.getEmail(); } // 数十个类似的getter... }

    问题:只是将方法调用转移,未真正封装行为

  2. 虚假朋友

    public class ReportService { private Order order; public void generate() { // 虽然order是成员变量,但深入其内部结构 for (Item item : order.getItems()) { // 处理item细节... } } }

    问题:形式上符合迪米特,实质上仍在破坏封装

  3. 滥用Law of Demeter

    // 过度应用导致代码晦涩 public class Order { private Customer customer; public void process(Processor processor) { processor.execute(this::getCustomerInfo); } private CustomerInfo getCustomerInfo() { return new CustomerInfo(customer); } }

    问题:引入了不必要的复杂性

健康度检查表

评估你的设计是否真正遵循迪米特:

  1. [ ] 类是否主要与以下对象交互:

    • 自己的成员变量
    • 方法参数
    • 自己创建的对象
    • 直接组件依赖
  2. [ ] 方法中是否避免超过1个"."的调用链?

  3. [ ] 是否避免将内部集合/数组直接暴露?

  4. [ ] 跨层调用是否通过明确定义的接口?

  5. [ ] 领域对象是否封装了相关行为而不仅是数据?

15. 性能考量:迪米特与效率的平衡

性能敏感场景的处理

  1. DTO投影优化

    // 避免多次查询的DTO构造 @Repository public interface OrderRepository extends JpaRepository<Order, Long> { @Query("SELECT new com.example.OrderSummary(o.id, c.name, o.total) " + "FROM Order o JOIN o.customer c WHERE o.status = 'COMPLETED'") List<OrderSummary> findCompletedOrders(); }
  2. 批量操作接口

    public interface OrderService { // 不符合迪米特但性能更好 Map<Long, BigDecimal> getOrderTotals(Collection<Long> ids); // 替代方案:分页查询 Page<OrderTotal> findOrderTotals(Pageable pageable); }
  3. CQRS模式应用

    // 命令端(严格封装) public class OrderCommandService { public void placeOrder(OrderCommand command) { // 严格验证和业务规则 } } // 查询端(适当放宽) public class OrderQueryService { public OrderView getOrderView(long id) { // 直接join多表获取展示数据 } }

性能测试指标

在放宽迪米特前进行测量:

  1. 关键路径分析

    @Benchmark public void testWithDemeter(Blackhole bh) { Order order = new Order(); bh.consume(order.getFormattedTotal()); } @Benchmark public void testWithoutDemeter(Blackhole bh) { Order order = new Order(); bh.consume(order.getTotal().getAmount() .setScale(2, RoundingMode.HALF_UP) .toString()); }
  2. JProfiler检测

    • 方法调用深度分析
    • 对象分配热点图
    • 调用树比较
  3. JMeter压力测试

    • 对比严格迪米特与优化版本的TPS
    • 测量99线响应时间差异

16. 语言特性进阶:Java特有实现技巧

Java模块系统的应用

  1. 模块信息配置

    // module-info.java module order.service { requires transitive customer.api; // 明确朋友关系 exports com.orders.service; opens com.orders.internal; // 谨慎使用 }
  2. 服务加载

    module payment.provider { provides PaymentService with CreditCardPayment; } module order.app { uses PaymentService; }

记录类(Record)的迪米特优势

// 定义不可变DTO public record OrderView( long id, String customerName, BigDecimal total ) {} // 使用处无需担心内部结构 public class ReportGenerator { public void generate(OrderView view) { // 直接使用字段,无需getter System.out.println(view.customerName()); } }

密封类(Sealed Class)控制朋友范围

public sealed interface PaymentResult permits Success, Failure, Pending { public record Success(String code) implements PaymentResult {} public record Failure(String reason) implements PaymentResult {} public record Pending() implements PaymentResult {} } public class PaymentService { // 调用方只需处理已知子类 public PaymentResult process(Payment payment) { // ... } }

17. 行业实践:知名框架中的迪米特应用

Spring框架实现

  1. 依赖注入原则

    • 通过@Autowired声明直接朋友
    • 避免手动查找依赖
  2. Controller最佳实践

    @Controller public class OrderController { private final OrderService service; // 唯一直接朋友 @PostMapping public ResponseEntity<?> create(@RequestBody OrderForm form) { // 不深入form的内部结构 return ResponseEntity.ok(service.createOrder(form)); } }

Hibernate实践

  1. 延迟加载设计

    @Entity public class Order { @ManyToOne(fetch = LAZY) // 避免自动加载关联 private Customer customer; public Customer getCustomer() { // 实际可能返回代理对象 } }
  2. DTO投影查询

    public interface OrderSummary { Long getId(); String getCustomerName(); // 不暴露整个Customer default String getSummary() {