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")存在几个严重问题:

  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) 12 3
类耦合度(CBO) 6 3
代码行数(LOC) 25 8
可测试性 需要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() {
    
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐