統一塑模語言(UML)是軟體架構的藍圖。在可使用的圖表套件中,類圖是定義系統靜態結構的基石。它描繪出類別、屬性、操作,以及將它們連結在一起的關鍵關係。在這些關係中,有兩個概念經常讓開發人員與架構師感到困惑:聚合與組合兩者都代表關聯的形式,但就所有權與生命週期管理而言,具有不同的語義重量。
選擇正確的關係模型不僅僅是語法上的偏好;它決定了物件之間如何互動、記憶體如何管理,以及系統如何處理失敗或刪除。誤解這些關係會導致脆弱的程式碼庫,其中物件的生命週期未被正確管理,進而造成懸空參考或資源洩漏。本指南剖析了聚合與組合的細微差別,為您在設計中應用它們提供明確的框架。

🔗 基礎:理解關聯
在區分聚合與組合之前,必須先理解基礎概念:關聯。在 UML 中,關聯是兩個或多個類別之間的關係,用以描述它們如何互動。這是關係中最一般的形式。
考慮一個簡單情境:一個學生類別與一個課程類別。學生註冊一門課程。這就是一種關聯。視覺表示方式是用一條實線連接兩個類別。關聯通常具有名稱(例如「註冊」)與多重性(例如一對多)。
關聯定義了如何類別之間如何互動。聚合與組合則進一步細化,用以定義如何它們共同存在的方式。它們是關聯的特殊化形式,暗示了一種「部分-整體」關係。然而,這種關係的強度差異顯著。
🔵 聚合:弱整體
聚合代表一種關係,其中一個類別是另一個類別的組成部分,但該部分可以獨立於整體存在。它通常被描述為一種較弱的「擁有」關係。其關鍵特徵是子物件的生命週期具有獨立性。
視覺表示
在 UML 類圖中,聚合以一條實線連接類別,並在「整體」類別的一端加上空心菱形符號表示。菱形指向包含該類別的一方。
- 符號:實線搭配空心菱形(◊)。
- 方向:菱形位於容器端。
生命週期獨立性
聚合的定義特徵是生命週期的獨立性。如果「整體」物件被銷毀,「部分」物件仍會繼續存在。它們是共享資源。
考慮一個系 和一個教授.
- 該系擁有許多教授。
- 然而,即使該系被解散或撤銷,教授也不會因此消失。
- 該教授可能轉至另一個系,或完全離開大學。
在這裡,系聚合了教授。教授並非由系獨佔擁有。它們是獨立的實體,僅恰好與該系有關聯。
實現邏輯
在物件導向程式設計中,這通常轉化為依賴注入或傳遞參考,而非在容器建構函式內建立新實例。容器持有對外部物件的參考。
- 建構函式: 容器不會建立部分。
- 設定方法: 通常透過設定方法來指派部分。
- 毀滅: 當容器被刪除時,參考會被移除,但垃圾回收器不會刪除部分。
🔴 組合:強勢整體
組合是一種更強的關聯形式。它代表一種「部分對整體」的關係,其中部分無法在沒有整體的情況下存在。這是一種獨佔所有權模型。如果整體被銷毀,部分也會隨之被銷毀。
視覺表示
組合在視覺上與聚合相似,但使用實心菱形。這個實心形狀代表了連結的強度。
- 符號: 實線搭配實心菱形(◆)。
- 方向: 菱形位於容器的一側。
生命週期依賴
部分的生命週期與整體的生命週期嚴格綁定。部分會隨著整體的建立與銷毀而建立與銷毀。
考慮一個房屋 和一個 房間.
- 房間是房屋的一部分。
- 如果房屋被拆除,房間作為功能單元便不復存在。
- 房間無法獨立於定義其邊界的結構而存在。
另一個經典範例是 汽車 和一個 引擎雖然引擎可以拆下維修,但在汽車的邏輯結構中,引擎是汽車存在不可或缺的組成部分。如果汽車被報廢,引擎也會被報廢(或作為該過程的一部分回收)。在嚴格的組合關係中,引擎並非同一邏輯範圍內其他汽車共享的資源。
實作邏輯
從實作角度來看,組合意味著容器負責零件的建立與銷毀。
- 建構函式: 容器建立零件的實例。
- 作用域: 零件通常是容器類別的私有成員。
- 摧毀: 當容器被銷毀時,零件會被明確地銷毀,或作為直接後果被垃圾回收。
📊 側邊比較
為了釐清兩者的差異,我們可以以結構化格式檢視兩種關係的特性。
| 特性 | 聚合 | 組合 |
|---|---|---|
| 關係類型 | 弱「擁有」 | 強「部分」 |
| 視覺符號 | 空心菱形 (◊) | 實心菱形 (◆) |
| 生命週期 | 獨立 | 依賴 |
| 擁有權 | 共用 | 獨佔 |
| 建立 | 外部 | 內部 |
| 銷毀 | 獨立 | 與整體自動同步 |
| 範例 | 系所 – 教授 | 房屋 – 房間 |
🧠 生命週期管理與記憶體
理解生命週期的影響對於穩健的軟體設計至關重要。在資源有限或手動記憶體管理的系統中,聚合與組合之間的差異決定了由誰負責清理工作。
聚合與共用參考
在聚合中,容器持有參考。多個容器可能持有對同一個子物件的參考。這在涉及共用服務或全域註冊表的情境中很常見。
- 情境: 一個
使用者物件與一個個人檔案物件。 - 行為: 一個
使用者擁有一個個人檔案。另一個SystemModule也可能持有對同一個Profile. - 影響:如果
User被刪除,則Profile必須仍可被SystemModule.
如果此關係被建模為組合,刪除 User將會刪除 Profile,可能導致 SystemModule的功能性遭到破壞。
組合與獨佔擁有
組合確保資源的封裝。整體是零件的唯一管理者。這降低了系統中無關部分之間的耦合度。
- 情境:一個
Document及其Pages. - 行為:一個
頁面屬於一個文件. - 影響: 如果
文件被關閉,則頁面數據將被丟棄。其他任何物件都不應持有對該特定頁面實例的參考。
此模型可防止資料完整性問題,即一個部分被不再「擁有」它的父物件修改。它強制建立明確的責任邊界。
🛠️ 實際設計情境
應用這些概念需要具備上下文。以下是選擇至關重要的具體情境。
1. 圖書館系統
想像一個管理圖書館的系統。
- 書籍與圖書館(聚合): 一本書可以在沒有圖書館的情況下存在。它可以被出售、遺失,或移至另一個圖書館。圖書館聚合其藏書中的書籍。
- 書籍與會員(關聯): 一位會員借閱一本書。這是一種暫時性的關聯,而非結構性關係。
2. 金融帳戶
考慮一個銀行應用程式。
- 帳戶與交易(組成): 交易記錄若沒有其所屬的帳戶則毫無意義。若帳戶被關閉,交易紀錄將作為一個整體被歸檔或銷毀。交易是帳戶狀態的一部分。
- 帳戶與客戶(聚合): 一位客戶可以擁有多个帳戶。若某個帳戶被關閉,客戶仍然存在。客戶聚合帳戶。
3. 使用者介面
在圖形使用者介面中,元件結構通常依賴於組成。
- 視窗與按鈕(組成): 窗口内的按鈕是該窗口佈局的一部分。如果窗口關閉,按鈕的狀態就不再重要。
- 窗口與工具列(聚合): 工具列可能在多個窗口之間共享。如果一個窗口關閉,工具列仍可供其他窗口使用。
⚠️ 常見陷阱與誤解
即使經驗豐富的設計師在將現實世界的概念映射到UML關係時也會犯錯。以下是一些應避免的常見錯誤。
1. 混淆組合與繼承
當組合(部分關係)更為合適時,人們很容易誤用繼承(是關係)。繼承暗示語義上的同一性,而組合則暗示結構上的依賴性。
- 錯誤:
汽車繼承引擎. - 正確:
汽車包含引擎(組合)。
繼承建立了一個 是關係。汽車不是引擎。它擁有引擎。混淆這兩者會導致難以維護的深層繼承層次結構。
2. 過度使用組合
嚴格的組合雖然強大,但可能導致僵化。如果你將所有內容都組合起來,就會失去靈活性。例如,將一個 記錄器 組合到每個類中,意味著你無法輕易替換日誌機制,除非重建物件樹。有時,對於可插入組件,聚合更為合適。
3. 忽略多重性
菱形符號並不會告訴你有多少個部分存在。你必須明確指定多重性(例如,0..1、1..*、0..*)。組合可以有零個部分,也可以有許多部分。關係的強度保持不變,但基數定義了結構。
4. 假設實現等同於圖示
一個常見錯誤是假設UML圖必須與代碼實現逐行對應。UML是一種模型,而非規格。你可能使用C++中的指針或Java中的引用來實現聚合。圖示傳達的是語義意圖,可能與底層記憶體管理略有不同。
🔍 深層考量
超越基本定義,這些關係對系統演進具有架構上的影響。
依賴注入與聚合
聚合與依賴注入(DI)天然契合。由於子物件獨立存在,因此可以在執行時期注入容器。這支援測試與模組化。您可以在不影響容器生命週期的情況下,替換注入的依賴項。
不可變物件與組合
組合常被用於不可變的資料結構中。如果一個結構由多個部分組成,且整體是不可變的,那麼這些部分通常也是不可變的。這確保了「整體」一旦建立,內部狀態便無法改變,從而強化組合的合約。
遞迴結構
聚合與組合都可以是遞迴的。一個資料夾可以包含檔案以及其他資料夾。這形成了樹狀結構。
- 聚合遞迴: 資料夾可以移動到另一個父資料夾(共享存在)。
- 組合遞迴: 資料夾是目錄樹的一部分。如果根節點被刪除,所有內容都會被刪除。
選擇正確的遞迴模型會影響您處理刪除操作的方式。組合簡化了刪除邏輯(刪除根節點 = 刪除所有內容)。聚合則需要遍歷以確保引用被清除,同時不會刪除共享的節點。
🎯 選擇指南
當您發現自己正在繪製類別圖並在這兩個選項之間猶豫時,請問這些具體問題。
- 零件是否能在沒有整體的情況下存在?
- 是 ➔ 使用聚合。
- 否 ➔ 使用組合。
- 零件是否可以屬於多個整體?
- 是 ➔ 使用聚合。
- 否 ➔ 使用組合。
- 誰負責零件的建立?
- 外部 ➔ 使用聚合。
- 內部(容器) ➔ 使用組合。
- 如果整體被刪除,會發生什麼事?
- 零件存活 ➔ 使用聚合。
- 零件消亡 ➔ 使用組合。
這些問題迫使您根據業務邏輯做出具體決策,而非抽象的設計模式。
📝 最佳實務總結
建模的清晰性可防止實現中的歧義。以下是維持高品質類圖的核心要點。
- 用聚合來處理共享資源: 當物件彼此獨立且可重複使用時。
- 用組合來處理獨佔部分: 當部分的存在若無整體則毫無意義時。
- 保持一致: 一旦決定使用某種模式,就應在整個系統中一致應用。除非有明確的語義原因,否則不要對類似的關係同時使用聚合與組合。
- 記錄意圖: 若生命週期較為複雜,請在圖中添加註解。UML 是一種溝通工具。
- 定期審查: 隨著需求變更,關係可能也會改變。若業務規則調整允許共享部分,原本的組合可能需要轉為聚合。
掌握這些差異,能讓您建構出具備韌性、可維護且邏輯清晰的系統。空心菱形與實心菱形之間的差異在視覺上微小,卻代表了軟體管理資料與控制流動的根本差異。透過關注這些細節,您能確保架構真實反映問題領域的本質。












