物件模型核心定位
描述系統靜態結構:類別/物件、屬性、操作、關係。目的在找出與應用程式密切相關的概念。
OMT 物件模型建構 5 步驟
步驟 1:確定物件類別
- 來源:需求文件、論域模型、使用者訪談、現有系統、設計模式目錄。
- 技巧:名詞識別法 (提取名詞 → 過濾 → 定義類別)、CRC 卡片 (類別-職責-合作者)。
- 產出:類別清單、簡短描述、責任概述。
步驟 2:定義資料字典
| 條目 | 內容 | 範例 |
| 類別名 | 業務術語、單數、PascalCase | CustomerOrder |
| 屬性 | 名稱、型別、可見性、限制、描述、衍生規則 | TotalAmount: decimal, public, >=0, 訂單總額, Sum(Items.Subtotal) |
| 操作 | 名稱、簽章、前置/後置條件、例外、描述 | Cancel(): void, [Status==Pending], OrderCancelledException, 取消訂單 |
| 關係 | 關聯/聚合/組合/繼承、角色、多重度、導航方向 | Customer 1 — 0..* Order (placedBy / orders) |
| 約束 | 不變條件、業務規則、唯一性、參考完整性 | Order.TotalAmount == Sum(OrderItem.Subtotal) |
步驟 3:用繼承組織與簡化
- 識別共同屬性/操作 → 提取至基類別 (一般化)。
- 識別特化行為 → 定義衍生類別 (特殊化)。
- 原則:is-a 關係 且 LSP 可滿足 才用繼承;否則用組合/介面。
- 避免過度繼承:深度 > 3 層、衍生類別僅覆寫 1-2 方法 → 改用策略模式/組合。
步驟 4:測試存取路徑
- 對每個查詢/操作需求,驗證從根物件 (Aggregate Root) 是否有導航路徑到達目標資料。
- 缺少路徑 → 新增關聯/導航屬性/查詢方法/索引。
- 路徑過長 (超過 3-4 跳) → 考慮重新設計聚合邊界或引入領域服務。
步驟 5:根據關係與功能分組建立模組
| 分組依據 | 說明 | 現代對應 |
| 業務能力 | 高內聚業務功能聚類 | DDD 限界上下文 / 微服務邊界 |
| 資料關聯密度 | 頻繁共同存取的類別分組 | 資料庫 Schema / 交易邊界 |
| 變更頻率 | 同步變更的類別分組 | 元件 / NuGet 套件 / 部署單元 |
| 團隊所有權 | 單一團隊端到端負責 | 單一倉庫 / 模組邊界 |
UML 類別圖核心符號速查
類別:
+------------------+
| ClassName |
+------------------+
| - field : Type |
| + property : Type|
| # method() : Ret |
+------------------+
關聯:─────────────────
│ 角色名 │
│ 1 0..* │
▼ ▼
[ClassA] [ClassB]
繼承:[Base] <|── [Derived] (空心三角箭頭指向基類別)
聚合:[Whole] ◇── [Part] (空心菱形指向整體)
組合:[Whole] ◆── [Part] (實心菱形指向整體)
依賴:[Client] -..-> [Supplier] (虛線箭頭)
現代 DDD 映射
| OMT 概念 | DDD 對應 | 關鍵差異 |
| 物件類別 | 實體 / 值物件 / 聚合根 / 領域事件 | 強調識別性、不可變性、一致性邊界 |
| 資料字典 | 無處不在的語言 / 術語表 / 程式碼即文件 | 程式碼屬性/建構子/方法簽章即字典 |
| 繼承簡化 | 介面隔離 / 策略模式 / 組合優於繼承 | 繼承極少用於領域層,多用於基礎設施 |
| 存取路徑 | 聚合根導航 / Repository 查詢 / 讀模型 | 嚴格限制跨聚合導航,CQRS 分離讀寫 |
| 模組劃分 | 限界上下文 / 子域 / 模組 | 明確邊界、上下文映射、防腐層 |
常見建模反模式
- 貧血模型:類別只有屬性無行為 (Getter/Setter 滿天飛) → 補充領域邏輯至實體方法。
- 上帝類別:單一類別承擔過多責任 → 拆分依 SRP/聚合根邊界。
- 循環依賴:類別雙向強關聯 → 引入介面/事件/中介者打破循環。
- 基礎設施滲透:領域類別直接依賴 EF Core/DBContext/HttpClient → 依賴倒置 + Repository 介面。