第 9 章

9.5 關聯類別、聚合與組合

物件導向分析與領域建模

9.5.1 關聯本身也可能具有資訊

兩個類別之間的關聯,可能有許多連帶資訊必須記錄;也就是說,兩個類別之間的關係具有自己的屬性、狀態或生命週期,單純一條關聯線便不足以完整表達語意。此時可使用關聯類別,將關聯本身需要保存的資訊納入模型;實務上也可以把這項關係建成一般類別,再分別連回原來的兩個類別。

例如,評審與報名之間並非只有「審查」關係。系統還需要知道分派時間與分派狀態,並由分派連結後續評分。因此,「評審分派」可作為具有獨立資訊的類別。下圖採用一般類別表示,使其屬性與後續關係更容易擴充。

text
classDiagram
  class Reviewer
  class Registration
  class ReviewAssignment {
    AssignedAt: DateTime
    Status: AssignmentStatus
  }
  Reviewer "1" -- "0..*" ReviewAssignment
  Registration "1" -- "0..*" ReviewAssignment
Mermaid 圖形預覽Mermaid 11.12
查看並複製 Mermaid 程式碼
mermaid
classDiagram
  class Reviewer
  class Registration
  class ReviewAssignment {
    AssignedAt: DateTime
    Status: AssignmentStatus
  }
  Reviewer "1" -- "0..*" ReviewAssignment
  Registration "1" -- "0..*" ReviewAssignment

9.5.2 聚合

聚合(aggregation)表示較弱的整體—部分關係,以空心菱形標示在整體端。部分可以脫離目前的整體而繼續存在,也可能改與另一個整體建立關係;因此,整體不控制部分的生命週期。

以Case M01的團隊與學生為例,可以把團隊理解為由學生組成的整體。團隊解散後,學生仍然存在;學生也可能在規則允許的前提下退出原團隊或加入其他團隊。若分析目的需要特別強調這種「成員可獨立存在」的整體—部分語意,可以使用聚合:

text
classDiagram
  class Team {
    TeamId: String
    Name: String
  }
  class Student {
    StudentId: String
    Name: String
  }
  Team o-- "3..5" Student : 由學生組成
Mermaid 圖形預覽Mermaid 11.12
查看並複製 Mermaid 程式碼
mermaid
classDiagram
  class Team {
    TeamId: String
    Name: String
  }
  class Student {
    StudentId: String
    Name: String
  }
  Team o-- "3..5" Student : 由學生組成

空心菱形位於Team端,表示團隊是整體、學生是部分;3..5則追溯至BR-01。這個例子只能說明聚合符號的語意,不能推導出學生能否同時加入多個團隊。若一般關聯已足以表達需求,便不必為了圖形豐富而使用聚合。UML聚合的語意比組合弱,實務上也容易被不同讀者作出不同解釋,因此應在文字中補充其業務意義。

9.5.3 組合

組合(composition)表示「整體與組成分」之間的關係,以實心菱形標示在「整體」端。「組成分」在同一時間只能隸屬於一個整體,而且其存在通常依賴該整體。若「整體」被刪除,「組成分」通常也會被刪除,或至少不再能以獨立業務物件存在。

為了清楚說明組合,可先使用一般性的「訂單與訂單明細」範例。「訂單明細」描述某張訂單中的商品、數量與成交單價;「訂單明細」不能離開「訂單」而單獨存在,也不能同時屬於兩張「訂單」,因此可使用組合:

text
classDiagram
  class Order {
    OrderId: String
    OrderedAt: DateTime
  }
  class OrderDetail {
    Quantity: Integer
    UnitPrice: Decimal
  }
  Order "1" *-- "1..*" OrderDetail : 包含
Mermaid 圖形預覽Mermaid 11.12
查看並複製 Mermaid 程式碼
mermaid
classDiagram
  class Order {
    OrderId: String
    OrderedAt: DateTime
  }
  class OrderDetail {
    Quantity: Integer
    UnitPrice: Decimal
  }
  Order "1" *-- "1..*" OrderDetail : 包含

實心菱形位於Order端,表示訂單控制訂單明細的歸屬與生命週期。這並不代表資料庫一定要使用連鎖刪除;類別圖表達的是業務語意,而不是直接指定資料庫實作。

在Case M01中,也可以用「團隊、團隊成員名單與學生」建立一個教學用的候選組合模型。其中,「學生」是可獨立存在的人員,不會因團隊解散而消失,因此團隊與學生之間不適合直接使用組合。相較之下,「團隊成員名單」及其中的「成員名單項目」只在特定團隊內具有意義:若該團隊不再存在,這份名單與名單項目也失去其業務歸屬。若需求確認系統必須把名單視為獨立管理的整體,便可提出下列候選模型:

text
classDiagram
  class Team {
    TeamId: String
    Name: String
  }
  class TeamRoster {
    RosterId: String
    UpdatedAt: DateTime
  }
  class TeamMemberEntry {
    JoinedAt: DateTime
    Role: String
  }
  class Student {
    StudentId: String
    Name: String
  }
  Team "1" *-- "1" TeamRoster : 擁有
  TeamRoster "1" *-- "3..5" TeamMemberEntry : 包含
  TeamMemberEntry "0..*" -- "1" Student : 對應
Mermaid 圖形預覽Mermaid 11.12
查看並複製 Mermaid 程式碼
mermaid
classDiagram
  class Team {
    TeamId: String
    Name: String
  }
  class TeamRoster {
    RosterId: String
    UpdatedAt: DateTime
  }
  class TeamMemberEntry {
    JoinedAt: DateTime
    Role: String
  }
  class Student {
    StudentId: String
    Name: String
  }
  Team "1" *-- "1" TeamRoster : 擁有
  TeamRoster "1" *-- "3..5" TeamMemberEntry : 包含
  TeamMemberEntry "0..*" -- "1" Student : 對應

圖中的實心菱形位於Team與TeamRoster端,表示名單及名單項目的生命週期分別依附於其整體;Student則以一般關聯連結名單項目,表示學生本人仍可獨立存在。3..5只反映BR-01所規定的每隊學生人數,並不涉及指導教師人數。至於系統是否需要獨立保存TeamRoster、是否記錄成員加入時間,以及一名學生能否出現在不同團隊的名單中,既有需求尚未確認,因此這張圖只能作為組合語意的示範,不能直接納入Case M01正式模型。

回到Case M01,若需求明確規定一份評分草稿只能隸屬於某項評審分派,而且分派取消時該草稿也必須失效,才可以考慮將「評審分派—評分草稿」建為組合。目前既有需求尚未完整規定取消分派與草稿保存方式,因此本章案例實作採用的正式模型仍採一般關聯,不把候選組合關係當成已確認規則。

9.5.4 選擇表示法的原則

選擇一般關聯、聚合或組合時,應檢查:

  • 該部分是否能脫離整體而存在?
  • 該部分是否可以同時隸屬於多個整體?
  • 整體消失時,該部分在業務上是否也必須消失?
  • 是否真的需要表達該部分,或者省略即可?
  • 是否加入了尚未確認的假設?

分析模型應選擇足以表達需求的最簡單關係。

表示法核心語意部分能否獨立存在Case M01中的使用方式
一般關聯兩個概念具有業務連結不由關聯決定適合多數已確認關係,例如團隊與指導教師
聚合較弱的整體—部分關係可以團隊與學生可作為符號教學候選,但一般關聯通常已足夠
組合整體控制組成分的歸屬與生命週期通常不可以團隊與團隊成員名單可作為候選示例,正式採用仍須需求確認

這三種表示法並不是由弱到強、愈強愈好的選項。選擇的標準是業務語意與需求證據,而不是圖形看起來是否完整。