组合实体模式:粗粒度对象、依赖对象、策略,以及Hibernate中的实体设计

组合实体模式,说白了就是「把一堆小对象打包成一个粗粒度的大对象」。我刚开始接触这个模式时,觉得它不就是个包装类吗?后来在项目中踩过坑才明白——它解决的是对象生命周期管理和依赖关系解耦的问题。

你想想看,在Hibernate里我们经常遇到这种情况:一个User对象,下面挂着Address、Phone、Role……每次保存User,你得手动去管这些子对象的持久化。代码写多了,你就会发现——其实这些子对象跟User是「同生共死」的关系。组合实体模式就是为这种场景设计的。

核心概念

组合实体模式包含三个角色:

  • 粗粒度对象(Composite Entity):对外暴露的「大对象」,比如User
  • 依赖对象(Dependent Object):生命周期依附于粗粒度对象的小对象,比如Address
  • 策略(Strategy):控制依赖对象如何被创建、更新、删除的规则

我在项目中遇到过一种典型场景:订单系统。Order是粗粒度对象,OrderItem是依赖对象。每次下单,OrderItem必须跟着Order一起创建;订单取消,OrderItem也得一起删除。这种「同生命周期」的关系,就是组合实体模式的最佳实践。

为什么需要组合实体模式?

直接操作细粒度对象有什么问题?我总结了几点:

  1. 客户端代码太啰嗦:每次操作都要处理一堆子对象
  2. 事务边界模糊:子对象和父对象的事务管理容易混乱
  3. 耦合度高:客户端直接依赖了多个细粒度对象

组合实体模式把这些问题都封装在内部。客户端只跟粗粒度对象打交道,依赖对象的管理由内部策略负责。

核心思想:粗粒度对象是门面,依赖对象是内部细节,策略是管理规则。客户端只看到门面,不关心内部细节。

代码示例:Java实现

先看一个简单的例子。假设我们有一个Employee实体,它包含Address和Contact两个依赖对象。

// 依赖对象:地址
public class Address {
    private String street;
    private String city;
    private String zipCode;
    
    // getters/setters 省略
}

// 依赖对象:联系方式
public class Contact {
    private String email;
    private String phone;
    
    // getters/setters 省略
}

// 粗粒度对象:员工
public class Employee {
    private String name;
    private Address address;
    private Contact contact;
    
    public Employee(String name) {
        this.name = name;
        this.address = new Address();
        this.contact = new Contact();
    }
    
    // 对外只暴露粗粒度方法
    public void setAddressInfo(String street, String city, String zip) {
        address.setStreet(street);
        address.setCity(city);
        address.setZipCode(zip);
    }
    
    public void setContactInfo(String email, String phone) {
        contact.setEmail(email);
        contact.setPhone(phone);
    }
    
    // 保存时,内部策略决定如何持久化
    public void save() {
        // 策略:先保存Employee,再保存Address和Contact
        System.out.println("保存员工: " + name);
        System.out.println("保存地址: " + address.getCity());
        System.out.println("保存联系方式: " + contact.getEmail());
    }
}

嗯,这里要注意:依赖对象的创建是在粗粒度对象的构造函数里完成的。这意味着依赖对象的生命周期完全由粗粒度对象控制。客户端不需要new Address(),也不需要关心Address什么时候被持久化。

策略模式在组合实体中的应用

组合实体模式经常和策略模式搭配使用。为什么?因为依赖对象的保存策略可能不同。比如:

  • 有些依赖对象需要级联保存(Cascade Save)
  • 有些依赖对象需要延迟加载(Lazy Load)
  • 有些依赖对象需要单独的事务管理

我曾经在一个项目中,把组合实体模式和策略模式结合,解决了「同一个实体在不同场景下有不同的持久化行为」的问题。举个例子:

// 策略接口
public interface PersistenceStrategy {
    void save(Employee employee);
    void delete(Employee employee);
}

// 级联保存策略
public class CascadePersistenceStrategy implements PersistenceStrategy {
    @Override
    public void save(Employee employee) {
        // 先保存主对象
        System.out.println("保存Employee");
        // 再保存依赖对象
        System.out.println("级联保存Address");
        System.out.println("级联保存Contact");
    }
    
    @Override
    public void delete(Employee employee) {
        // 先删除依赖对象
        System.out.println("级联删除Contact");
        System.out.println("级联删除Address");
        // 再删除主对象
        System.out.println("删除Employee");
    }
}

// 延迟加载策略
public class LazyPersistenceStrategy implements PersistenceStrategy {
    @Override
    public void save(Employee employee) {
        // 只保存主对象,依赖对象延迟保存
        System.out.println("保存Employee(依赖对象延迟保存)");
    }
    
    @Override
    public void delete(Employee employee) {
        System.out.println("标记删除Employee(依赖对象由GC处理)");
    }
}

你看,策略模式让组合实体的行为变得灵活。客户端只需要传入不同的策略,粗粒度对象的行为就变了。这就是「组合实体 + 策略」的威力。

Hibernate中的实体设计

说到Hibernate,组合实体模式其实无处不在。Hibernate的实体设计天然支持这种模式:

Hibernate注解 对应组合实体模式角色 说明
@Entity 粗粒度对象 对外暴露的持久化实体
@Embeddable 依赖对象 生命周期依附于@Entity,不能独立存在
@OneToMany(cascade = ALL) 依赖对象集合 级联操作,同生命周期
@Embedded 依赖对象引用 值类型嵌入,完全由父对象管理

我个人习惯在Hibernate中使用@Embeddable来实现依赖对象。为什么?因为@Embeddable的对象不能独立查询,它的生命周期完全由父实体控制。这正好符合组合实体模式的定义。

小技巧:在Hibernate中,如果你发现某个实体总是和另一个实体同时出现、同时消失,那就应该考虑用@Embeddable把它变成依赖对象。这样可以减少表的数量,也简化了代码。

避坑指南

我曾经犯过一个错误:把所有的关联关系都设计成组合实体模式。结果导致一个订单实体里嵌入了十几个依赖对象,代码变得极其臃肿。后来我总结了一条原则——只有「同生命周期」的关系才适合用组合实体模式。如果两个对象的生命周期不同,比如User和Log,那就应该用关联关系,而不是组合关系。

另外,组合实体模式不适合以下场景:

  • 依赖对象需要被多个粗粒度对象共享
  • 依赖对象需要独立查询和操作
  • 依赖对象的生命周期比粗粒度对象长

SVG结构图

下面这张图展示了组合实体模式的核心结构:

组合实体模式结构图 客户端 粗粒度对象 (Composite Entity) 依赖对象1 (Dependent) 依赖对象2 (Dependent) 策略 (Strategy) 客户端只与粗粒度对象交互 依赖对象的生命周期由粗粒度对象管理 策略控制依赖对象的创建、更新、删除规则

总结

组合实体模式不是什么高深的技术,它就是一套「打包管理」的思路。把那些生命周期一致、操作行为一致的小对象,打包成一个粗粒度对象,对外提供简洁的接口。内部用策略来控制细节,用依赖对象来承载数据。

我在实际项目中,最常用的就是Hibernate的@Embeddable + @Embedded组合。它天然支持组合实体模式,而且代码写起来很清爽。如果你正在做实体设计,不妨试试这个思路——先问自己:这些对象是不是同生共死的?如果是,那就打包起来。

一句话记住:组合实体模式 = 粗粒度对象(门面) + 依赖对象(内部细节) + 策略(管理规则)。


公众号:蓝海资料掘金营,微信deep3321