三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

别再死记硬背UML箭头了!用Java/Spring Boot实战案例,5分钟搞懂类图四种关系

别再死记硬背UML箭头了!用Java/Spring Boot实战案例,5分钟搞懂类图四种关系

别再死记硬背UML箭头了!用Java/Spring Boot实战案例,5分钟搞懂类图四种关系

记得刚入行时,每次面试官问到UML类图中的空心菱形和实心菱形有什么区别,我的大脑就像突然断网的浏览器——一片空白。直到参与了一个电商系统的重构项目,当我在白板上画出订单模块的类图时,才真正理解这些箭头背后的生命力。今天,我们就用Spring Boot构建一个简化的订单系统,让类图关系从纸面跃入代码。

1. 从需求到类图:订单系统的四种关系拆解

假设我们需要开发一个订单处理模块,核心场景是用户下单购买商品。通过这个案例,可以清晰展示四种UML关系的典型应用:

// 基础类定义 public class User { private String userId; private List<Order> orders; // 关联关系 } public class Product { private String sku; private BigDecimal price; } public class Order { private String orderId; private List<OrderItem> items; // 组合关系 private PaymentService paymentService; // 依赖关系 } public class OrderItem { private Product product; // 聚合关系 private int quantity; }

1.1 最弱的羁绊:依赖关系(Dependency)

当Order需要调用PaymentService完成支付时,就形成了典型的依赖关系。这种临时性的使用关系在Spring Boot中常表现为:

@Service public class OrderService { // 方法参数注入形成依赖 public void processPayment(Order order, PaymentService payment) { payment.process(order.getTotal()); } }

关键特征:

  • 虚线箭头指向被依赖方
  • 生命周期无绑定关系
  • 实现方式包括:
    • 方法参数传递
    • 局部变量创建
    • 静态方法调用

1.2 稳定的合作:关联关系(Association)

用户与订单之间的长期持有关系,在代码中表现为成员变量:

@Entity public class User { @Id private String userId; @OneToMany(mappedBy = "user") private List<Order> orders = new ArrayList<>(); // 双向关联 }

关联强度对比表:

类型代码表现UML表示Spring Boot示例
单向关联单方持有引用单箭头实线@ManyToOne
双向关联双方互相持有引用无箭头实线@OneToMany+@ManyToOne
自关联类包含自身类型属性指向自身的箭头树形结构节点定义

2. 整体与部分:聚合与组合的微妙差异

2.1 可拆卸的零件:聚合关系(Aggregation)

订单项(OrderItem)和商品(Product)的关系就像购物车里的商品——商品离开订单依然存在:

public class OrderItem { @ManyToOne private Product product; // 空心菱形指向Product private int quantity; // 商品可被多个订单共享 public OrderItem(Product product, int qty) { this.product = product; this.quantity = qty; } }

2.2 生死与共:组合关系(Composition)

订单与订单项的关系则是强绑定——删除订单时,订单项也应随之销毁:

public class Order { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "order_id") private List<OrderItem> items = new ArrayList<>(); // 实心菱形指向OrderItem public void addItem(Product p, int qty) { items.add(new OrderItem(p, qty)); // 生命周期绑定 } }

内存管理对比实验:

// 聚合关系测试 Product phone = new Product("iPhone", 9999); OrderItem item1 = new OrderItem(phone, 1); OrderItem item2 = new OrderItem(phone, 2); // 组合关系测试 Order order = new Order(); order.addItem(phone, 1); // 内部创建OrderItem

3. Spring Boot中的关系映射实战

3.1 JPA注解与UML对应关系

Spring Data JPA提供了直观的关系映射:

@Entity public class Order { @Id private Long id; @ManyToOne // 关联关系 private User user; @OneToMany(mappedBy = "order", cascade = ALL) // 组合关系 private List<Payment> payments = new ArrayList<>(); } @Entity public class Payment { @ManyToOne // 双向关联 private Order order; @Transient // 依赖关系 private PaymentGateway gateway; }

3.2 架构设计中的关系选择

在微服务设计中,不同关系对应不同的拆分策略:

  1. 组合关系:建议放在同一服务内(如订单和订单项)
  2. 聚合关系:可跨服务引用(如订单和商品)
  3. 关联关系:考虑服务拆分时的数据同步
  4. 依赖关系:适合跨服务调用

4. 调试技巧:从运行时看关系差异

使用JVM工具观察不同关系的内存表现:

# 查看对象引用关系 jmap -histo <pid> | grep -E 'Order|Product' # 堆转储分析 jcmd <pid> GC.heap_dump /tmp/dump.hprof

常见误区排查:

  • 误将组合关系实现为聚合 → 导致内存泄漏
  • 循环依赖问题 → 使用DTO打破关联
  • JPA缓存与对象关系 → 及时刷新会话

理解这些关系后,下次设计系统时可以先在白板上画出类图,再考虑:

  1. 哪些对象应该同生共死(组合)
  2. 哪些模块可以独立变化(依赖)
  3. 哪些数据需要双向感知(关联)

当你能流畅地在类图和代码之间切换视角时,就真正掌握了面向对象设计的精髓。

← 返回列表