首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 聚合與組合關係深度解析:UML 建模、代碼實現與領域建模指南

     編輯:Java 相關知識

在面向對象設計與 UML 建模中,「聚合」與「組合」是兩種特殊的「擁有」關係,區分它們對於領域模型準確性、生命週期管理、資料庫外鍵設計至關重要。本文從定義、UML 符號、代碼體現、業務語義、資料庫映射五個維度徹底講透。

一、核心定義對比

維度聚合組合
語義強度弱「擁有」:Has-a,整體與部分可分離強「擁有」:Contains-a,部分不可離開整體
生命週期部分獨立,可被多個整體共享部分依賴整體,隨整體創建/銷毀
所有權共享所有權獨占所有權
UML 符號空心菱形 ◇──實心菱形 ◆──
菱形位置在整體類一側在整體類一側

二、經典業務例子

  • 組合:訂單 ◆── 訂單項、房子 ◆── 房間、人 ◆── 身份證號、部門 ◆── 內部項目。訂單刪除 → 訂單項必須刪除;房間不能獨立於房子存在。
  • 聚合:教師 ◇── 學生、汽車 ◇── 引擎(可拆卸移植)、部門 ◇── 員工(員工可調動)、圖書館 ◇── 圖書(圖書可借出/轉贈)。教師離職學生不消失;引擎可裝到另一輛車。

三、代碼層面體現

1. 組合:內部實例化、構造器注入、無 setter、生命週期綁定

public class Order {
    private final List<OrderItem> items = new ArrayList<>(); // final,不可替換集合引用
    
    public Order() {} // 無參構造
    
    public void addItem(Product product, int quantity) {
        // 內部創建,外部不可直接 new OrderItem
        items.add(new OrderItem(product, quantity, this)); // 反向引用可選
    }
    
    public List<OrderItem> getItems() {
        return Collections.unmodifiableList(items); // 防御性拷貝/不可修改視圖
    }
    
    // 無 setItems(),無公共構造器暴露 OrderItem 創建
}

// OrderItem 無獨立生命週期,通常不單獨持久化為聚合根
@Entity
public class OrderItem {
    @Id @GeneratedValue
    private Long id;
    
    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "order_id", nullable = false) // 外鍵非空,級聯刪除
    private Order order; // 關聯整體
    
    // 業務屬性...
}

2. 聚合:外部創建、Setter/構造器注入、可共享、獨立持久化

public class Department {
    private final Set<Employee> employees = new HashSet<>();
    
    public void addEmployee(Employee emp) {
        employees.add(emp);
        emp.setDepartment(this); // 雙向關聯維護
    }
    
    public void removeEmployee(Employee emp) {
        employees.remove(emp);
        emp.setDepartment(null);
    }
    
    public Set<Employee> getEmployees() {
        return Collections.unmodifiableSet(employees);
    }
}

@Entity
public class Employee {
    @Id @GeneratedValue
    private Long id;
    
    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "dept_id") // 可為空,員工可無部門
    private Department department;
    
    // 業務屬性...
}

四、UML 類圖表示


@startuml
class Order {
    - items: List
    + addItem()
    + getItems()
}

class OrderItem {
    - order: Order
}

class Department {
    - employees: Set
    + addEmployee()
    + removeEmployee()
}

class Employee {
    - department: Department
}

Order *-- OrderItem : 組合 (實心菱形)
Department o-- Employee : 聚合 (空心菱形)
@enduml

五、資料庫設計映射(JPA/Hibernate 為例)

關係外鍵位置級聯操作可空性刪除行為
組合從表CascadeType.ALL + orphanRemoval=trueNOT NULL刪主表 → 級聯刪從表
聚合從表通常無級聯,或 PERSIST/MERGENULLABLE刪主表 → 從表外鍵置 NULL 或保留
// 組合:Order 刪除時自動刪除所有 OrderItem
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();

// 聚合:Department 刪除不影響 Employee,僅置空 dept_id
@OneToMany(mappedBy = "department", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<Employee> employees = new HashSet<>();

六、領域驅動設計 (DDD) 中的聚合根視角

DDD 將「組合」關係內的整體稱為聚合根,部分稱為實體/值對象。聚合根是外部訪問的唯一入口,保證不變條件。

  • 聚合根:Order、Department(擁有全局標識,獨立持久化)
  • 內部實體:OrderItem(僅在聚合內部有標識,隨聚合根生命週期)
  • 值對象:Address、Money(無標識,不可變,可共享)

原則:外部只能持有聚合根引用,不可直接引用/修改內部實體。所有業務操作通過聚合根方法完成(如 order.addItem() 而非 new OrderItem())。

七、常見建模錯誤

  1. 將聚合誤建模為組合:如 User ◆── Address。用戶刪除地址不應刪除(地址可能被其他用戶引用、歷史訂單保留)。正確:User ◇── Address 或值對象嵌入。
  2. 將組合誤建模為聚合:如 Order ◇── OrderItem。導致 OrderItem 可獨立存在、被其他 Order 引用、刪除 Order 時孤兒數據殘留。
  3. 雙向關聯未維護:department.addEmployee(emp) 必須同時設置 emp.setDepartment(this),否則關係斷裂。
  4. 暴露內部集合可變引用:return items; 允許外部 items.clear() 繞過業務邏輯。必須返回 unmodifiableList 或防御性拷貝。

八、總結

聚合與組合的本質區別在於部分對象的生命週期是否依賴整體。組合 = 強生命週期綁定 + 獨占擁有;聚合 = 弱關聯 + 可共享 + 獨立生命週期。在代碼中通過 final 集合、無 setter、內部實例化、級聯刪除體現組合;通過可空外鍵、Setter 注入、無級聯刪除體現聚合。DDD 聚合根模式進一步強化了組合的封裝性與一致性邊界。正確建模,領域邏輯才清晰、持久化才正確、演進才可控。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved