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为了获取城市信息,需要依次通过Order→Customer→Address→getCity()的调用链。这种"火车残骸"式的代码(称为"Train Wreck")存在几个严重问题:
- 过度暴露实现细节:上层服务需要了解下层所有对象的结构
- 脆弱性:中间任何一个环节的修改都会导致调用方代码变更
- 难以测试:需要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) { // 业务逻辑... } } } }这种模式的危害在于:
- 破坏封装性:外部可以直接修改School的内部集合
- 业务逻辑泄露:教师数量的判断应该属于School的职责
- 难以扩展:如果需要过滤条件变化,需要修改所有调用方
重构方案:提供行为而非数据
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统一维护
- 业务语义更加清晰("处理大型部门")
集合处理的三种正确姿势
防御性拷贝(适用于不可变数据):
public List<Department> getDepartments() { return new ArrayList<>(departments); }视图方法(Java 9+):
public List<Department> getDepartmentsView() { return List.copyOf(departments); }行为暴露(推荐方式):
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(); // 各种报表生成代码... } }这种"上帝类"的问题在于:
- 违反单一职责原则:一个类承担过多功能
- 高耦合:需要了解Employee的所有细节
- 难以维护:任何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交互
服务类设计的黄金法则
角色接口原则:按调用者角色定义接口,而非按实现
public interface PayrollProcessor { PayrollResult process(Employee emp); }依赖注入:避免静态方法,提高可测试性
public class PayrollService { private final PayrollProcessor processor; public PayrollService(PayrollProcessor processor) { this.processor = processor; } }上下文参数化:将运行时数据通过参数传递
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()); } } }违反点分析:
CollegeEmployee作为局部变量出现(陌生人)- 学院员工的打印逻辑应该属于
CollegeManager - 学校总部员工打印逻辑与学院员工打印耦合
分步骤重构过程
第一步:识别直接朋友
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. 高级应用:迪米特法则在分层架构中的实践
分层架构中的朋友关系
在典型的三层架构中,各层的"朋友"定义如下:
表示层:
- 直接朋友:服务接口、DTO
- 陌生人:领域对象、数据访问对象
业务层:
- 直接朋友:领域模型、仓储接口
- 陌生人:具体技术实现(如JDBC、Redis)
数据层:
- 直接朋友:数据库连接、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中,迪米特法则表现为:
聚合根封装:外部只能通过聚合根访问内部对象
public class Order { private List<OrderItem> items; public void addItem(Product product, int quantity) { // 封装所有业务规则 if (canAddItem(product)) { items.add(new OrderItem(product, quantity)); } } }限界上下文隔离:不同上下文通过防腐层交互
public class InventoryService { private final InventoryClient client; public boolean isProductAvailable(ProductId id) { // 转换为外部系统理解的DTO InventoryRequest request = convert(id); return client.checkStock(request); } }领域事件通知:通过事件而非直接方法调用
public class Order { public void cancel() { this.status = CANCELLED; registerEvent(new OrderCancelled(this.id)); } }
6. 性能与可维护性的平衡艺术
何时可以放宽迪米特法则?
虽然迪米特法则能提高代码质量,但在以下场景可以适当放宽:
DTO/VO对象:数据传输对象天然需要暴露字段
public class UserDto { public String username; // 公开字段可以接受 public String email; }性能关键路径:避免不必要的封装导致性能下降
// 游戏开发中的典型例子 public class ParticleSystem { public Particle[] particles; // 直接暴露数组以提高性能 }内部框架代码:框架底层需要直接操作对象
// ORM框架中的实体操作 public class EntityAccessor { public Object getField(Object entity, String fieldName) { // 通过反射直接访问字段 } }
测量耦合度的三个指标
响应度(Response For Class, RFC):
- 计算方法:类的方法集合 + 这些方法直接调用的其他方法数量
- 健康值:RFC < 50
耦合度(Coupling Between Objects, CBO):
- 计算方法:与当前类直接关联的类数量
- 健康值:CBO < 10
信息流(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个"."的调用链?
是否将集合/数组直接返回给调用方?
工具类是否知道太多领域对象的细节?
IntelliJ IDEA检测技巧
方法链检测:
- 设置 → Editor → Inspections → Java → Class metrics → Method chain
- 设置最大链长度为1
耦合度检测:
- 使用"Analyze → Analyze Dependencies"查看类依赖
- 使用"Diagrams → Show Dependencies"生成可视化图表
自定义Live Template:
// 快速生成符合迪米特的委托方法 public $RETURN_TYPE$ $DELEGATE_METHOD$($PARAMS$) { return this.$DELEGATE_FIELD$.$ORIGINAL_METHOD$($ARGS$); }
团队代码审查要点
重点审查:
- 超过3层的方法调用链
- 返回集合/数组的getter方法
- 静态工具类对领域对象的操作
审查话术示例:
- "这个服务类是否可以直接调用DAO?"
- "这个长方法调用链能否封装到下层对象?"
- "外部类是否需要知道这么多内部细节?"
渐进式改进策略:
- 第一阶段:消除明显的"火车残骸"代码
- 第二阶段:重构返回集合的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原则的关系
互补关系:
单一职责原则(SRP):
- 迪米特帮助识别类是否承担过多职责
- 一个类如果知道太多"陌生人",可能违反SRP
开闭原则(OCP):
- 迪米特通过减少耦合使系统更易扩展
- 修改被隔离在最小范围内
接口隔离原则(ISP):
- 两者都强调最小化依赖
- ISP关注接口粒度,迪米特关注对象交互
依赖倒置原则(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. 工具链支持:自动化检测与重构
静态分析工具
SpotBugs规则:
LOD_LACK_OF_DEMETER:检测明显违反迪米特的代码- 配置示例:
<Match> <Bug pattern="LOD_LACK_OF_DEMETER" /> <Priority value="2" /> </Match>
SonarQube指标:
java:S1201:方法链过长java:S2384:公共方法返回内部数组- 质量阈设置:
"rules": { "java:S1201": {"level": "CRITICAL"}, "java:S2384": {"level": "MAJOR"} }
ArchUnit测试:
@ArchTest public static final ArchRule demeter_rule = ArchRuleDefinition.noClasses() .should().accessClassesThat().resideInAnyPackage("..internal..") .orShould().callMethodWhere(JavaMethod.Predicates.chainCall());
重构技术
提取方法对象:
// 重构前 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(); // 处理逻辑... } }引入参数对象:
// 重构前 public void register(String name, String email, String street, String city) { // 需要知道所有参数细节 } // 重构后 public void register(RegistrationForm form) { // 只需与form对象交互 }替换委托为继承(谨慎使用):
// 重构前 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; } }主要问题:
- 深入订单项、产品、定价、会员等多层对象
- 价格计算逻辑分散在各层对象中
- 任何底层模型修改都会影响此服务
分阶段重构过程
阶段一:封装产品价格计算
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) | 12 | 3 |
| 类耦合度(CBO) | 6 | 3 |
| 代码行数(LOC) | 25 | 8 |
| 可测试性 | 需要mock 5个类 | 只需mock 2个类 |
业务收益:
- 价格计算逻辑现在由各领域对象自己维护
- 新增折扣类型只需修改对应类,不影响服务层
- 单元测试更容易编写和维护
- 业务语义更加清晰明了
11. 设计模式中的迪米特应用
典型设计模式实现
外观模式(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交互,无需了解子系统细节
中介者模式(Mediator):
public class AirTrafficControl { private List<Aircraft> aircrafts; public void requestLanding(Aircraft aircraft) { // 协调所有飞机交互 aircrafts.forEach(a -> a.adjustCourse()); aircraft.clearToLand(); } }效果:飞机之间不直接通信,通过控制塔中介
代理模式(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. 微服务架构中的迪米特法则
服务边界的迪米特应用
服务接口设计:
- 提供粗粒度API,避免"聊天式"交互
- 示例:
// 不符合迪米特 @GetMapping("/users/{id}/address/street") // 符合迪米特 @GetMapping("/users/{id}/profile")
DTO设计原则:
- 包含完整的上下文信息
- 避免嵌套过深的结构
- 示例:
// 不符合迪米特 public class OrderDto { public CustomerDto customer; // 嵌套过深 } // 符合迪米特 public class OrderDto { public String customerName; public String shippingAddress; }
事件驱动交互:
// 订单服务发布事件 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) { // 只需处理事件数据,不了解订单内部 } }
服务网格中的实践
Sidecar模式:
- 每个服务只与自己的sidecar交互
- sidecar处理与其他服务的通信细节
服务契约:
- 定义清晰的接口规范
- 使用Protobuf/OpenAPI等IDL语言
混沌工程验证:
- 随机断开服务间连接
- 验证是否遵循了最小知识原则
13. 测试策略:如何验证迪米特合规性
单元测试验证
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交互 }反射检测法:
@Test public void checkNoForbiddenDependencies() { Set<Class<?>> allowed = Set.of(Order.class, Logger.class); for (Field field : service.getClass().getDeclaredFields()) { assertTrue(allowed.contains(field.getType())); } }
集成测试策略
组件边界测试:
@SpringBootTest public class ComponentBoundaryTest { @Autowired private ApplicationContext context; @Test public void checkLayerDependencies() { for (String beanName : context.getBeanDefinitionNames()) { Object bean = context.getBean(beanName); // 验证Controller不直接依赖Repository等规则 } } }架构测试:
@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. 反模式警示:这些"优化"其实在破坏迪米特
常见错误实践
过度封装:
// 反面例子:无意义的封装 public class Order { private Customer customer; public String getCustomerName() { return customer.getName(); } public String getCustomerEmail() { return customer.getEmail(); } // 数十个类似的getter... }问题:只是将方法调用转移,未真正封装行为
虚假朋友:
public class ReportService { private Order order; public void generate() { // 虽然order是成员变量,但深入其内部结构 for (Item item : order.getItems()) { // 处理item细节... } } }问题:形式上符合迪米特,实质上仍在破坏封装
滥用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个"."的调用链?
[ ] 是否避免将内部集合/数组直接暴露?
[ ] 跨层调用是否通过明确定义的接口?
[ ] 领域对象是否封装了相关行为而不仅是数据?
15. 性能考量:迪米特与效率的平衡
性能敏感场景的处理
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(); }批量操作接口:
public interface OrderService { // 不符合迪米特但性能更好 Map<Long, BigDecimal> getOrderTotals(Collection<Long> ids); // 替代方案:分页查询 Page<OrderTotal> findOrderTotals(Pageable pageable); }CQRS模式应用:
// 命令端(严格封装) public class OrderCommandService { public void placeOrder(OrderCommand command) { // 严格验证和业务规则 } } // 查询端(适当放宽) public class OrderQueryService { public OrderView getOrderView(long id) { // 直接join多表获取展示数据 } }
性能测试指标
在放宽迪米特前进行测量:
关键路径分析:
@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()); }JProfiler检测:
- 方法调用深度分析
- 对象分配热点图
- 调用树比较
JMeter压力测试:
- 对比严格迪米特与优化版本的TPS
- 测量99线响应时间差异
16. 语言特性进阶:Java特有实现技巧
Java模块系统的应用
模块信息配置:
// module-info.java module order.service { requires transitive customer.api; // 明确朋友关系 exports com.orders.service; opens com.orders.internal; // 谨慎使用 }服务加载:
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框架实现
依赖注入原则:
- 通过
@Autowired声明直接朋友 - 避免手动查找依赖
- 通过
Controller最佳实践:
@Controller public class OrderController { private final OrderService service; // 唯一直接朋友 @PostMapping public ResponseEntity<?> create(@RequestBody OrderForm form) { // 不深入form的内部结构 return ResponseEntity.ok(service.createOrder(form)); } }
Hibernate实践
延迟加载设计:
@Entity public class Order { @ManyToOne(fetch = LAZY) // 避免自动加载关联 private Customer customer; public Customer getCustomer() { // 实际可能返回代理对象 } }DTO投影查询:
public interface OrderSummary { Long getId(); String getCustomerName(); // 不暴露整个Customer default String getSummary() {