四步 OOA 工作流

這是標準工作流。按順序進行。別跳過步驟。別合併步驟。


步驟 1:領域詞彙與關係

目標

萃取並命名領域的核心概念,並闡明它們如何關聯

要產出什麼

  1. 詞彙表,依概念類型分組。常見分組:

    • 主體類(Entities) — 行為者、具識別的事物
    • 時間類(Temporal) — 班表、時間窗、週期
    • 能力類(Capabilities) — 實體能做什麼
    • 匹配類(Rules / Matching) — 連結概念的業務規則
    • 查詢類(Query views) — 唯讀投影
    • 操作關係類(Operating relations) — 連結多個實體的依賴
  2. 關係草圖,用純文字/箭頭(先別用 Mermaid)。標出方向。例如:

    Resource ──belongs to──> WorkCenter
    Resource ──has 1──> Schedule ──composed of──> Shift[]
    
  3. 值得早點提出的設計分歧 — 關係方向、繼承 vs. 組合、某概念「住」在哪(哪個 package/aggregate)。

關鍵行為

  • 先用使用者的詞彙。 若他說「資源」(resource),別默默改名成「Entity」。他的用字帶有領域意義。
  • 雙語名詞:若使用者用中文書寫,中文 + 英文並列(例如:班表 / Schedule)。這有助於之後的 Java 識別碼命名。
  • 別引入使用者沒提到的概念,除非你明白標出來:「我建議也引入 OperatingRelation 來表達 X——這符合你的心智模型嗎?」
  • 攤開使用者描述留下的模糊。 常見模糊:
    • X 是 entity 還是 Value Object?
    • X 由 Y 擁有,還是只是與 Y 關聯?
    • X 有識別,還是可互換?

結束步驟 1

2-3 個針對性分歧作結,讓使用者拍板。別問「還有別的嗎?」——問具體問題。多選分歧用 ask_user_input_v0。例如:

  • 「Resource 該用繼承(Machine/Mold/Worker 子類別)還是組合(Resource 帶一個 type 欄位 + 行為 strategy)?」
  • 「自動化程度是 Machine 的屬性,還是其實是該住在別處的排程關注點?」
  • 「匹配該由需求端驅動(PartNumber → 所需資源),還是供給端驅動(Resource → 它能做什麼)?」

步驟 2:類別圖(Mermaid)

目標

把詞彙轉譯成帶屬性、方法與關係的視覺化類別結構

要產出什麼

一個 Mermaid classDiagram 區塊,顯示:

  • 步驟 1 的所有核心類別
  • 帶型別的屬性(用 + public、- private、# protected——但 OOA 階段偏好全用 -;我們還沒在指定 API)
  • 關鍵方法(只要簽名,不要方法體)
  • 多重性方向的關係
  • 適當處的列舉(用 <<enumeration>>
  • 介面用 <<interface>>、抽象類別用 <<abstract>>

須遵循的 Mermaid 慣例

classDiagram
    direction TB

    class ClassName {
        <<abstract>>            %% 或 <<interface>> 或 <<enumeration>>
        -fieldName: FieldType
        +methodName(param) ReturnType
    }

    %% 關係:
    ClassA <|-- ClassB          %% B 繼承 A
    InterfaceA <|.. ClassB      %% B 實作 A
    ClassA "1" *-- "*" ClassB   %% A 由多個 B 組成(強擁有)
    ClassA "1" o-- "*" ClassB   %% A 聚合多個 B(弱擁有)
    ClassA "*" --> "1" ClassB   %% A 與 B 關聯

方向指引:

  • 多數圖用 TB(上到下)
  • 鏈狀或序列用 LR(左到右)

%% ========== 群組名 ========== 註解把相關類別分組。這讓大圖更易讀。

須避免的常見陷阱

  • 別顯示每個 getter/setter。 OOA 階段的方法是查詢與關鍵命令。存取器留到實作階段。
  • 別任意決定關係方向。 方向編碼了擁有與依賴——要弄對。
  • 別加沒有目的的欄位/方法。 若你解釋不了某方法為何存在,就刪掉。
  • 別混用抽象層次。 別把 equals() 放在 findCandidates() 旁邊。前者是實作,後者是 domain。

子圖

對複雜系統,在完整圖之後,加 1-2 張聚焦的子圖,單獨呈現關鍵子系統。例如:

  • 完整圖:30 個類別
  • 子圖 1:行事曆子系統(5 個類別,放大)
  • 子圖 2:Specification 樹(4 個類別,呈現組合)

結束步驟 2

別自動推進到步驟 3。問:

  • 「這張圖符合你的心智模型嗎?」
  • 「有什麼要增、刪、或重塑的嗎?」

在前進前,等待確認或回饋。


步驟 3:SOLID 審查 + 設計模式

目標

就 SOLID 合規性稽核設計,並在徵得使用者同意下提出具體設計模式

要產出什麼

部分 A:SOLID 檢查清單

對每個原則(SRP、OCP、LSP、ISP、DIP),說明:

  • 設計在哪裡滿足它 — 從圖中舉具體例子
  • ⚠️ 潛在違反或風險 — 以及哪些徵兆日後會浮現它們
  • 💡 若存在違反的重構建議

別只說「符合 SRP」——點名哪個類別為什麼。例如:

SRPSchedule 只處理時間(baseline + 例外組合);Capability 只處理一個資源能做什麼。

⚠️ ISP 風險:對 Worker 而言,Resource.hasCapability(Process) 感覺很怪——工人是以技能在思考,不是以工序。要嘛改名,要嘛只為 Machine/Mold 萃取一個 ProcessExecutor 介面。

部分 B:設計模式提案

對每個能改善設計的模式,以表格呈現:

#模式用於必要性
1Specification組合匹配規則🔥 強烈推薦
2Repository解耦資料存取✅ DDD 標準
3Strategy各型別行為不同⭐ 選用

必要性圖例:

  • 🔥 強烈推薦 — 沒有它設計會有真實痛點
  • ✅ 標準做法 — 自然契合、成本低
  • ⭐ 選用 — 有用但增加複雜度,到需要時再做

採用前先問。ask_user_input_v0

「要我納入哪些模式?(a) 全部,(b) 只要 Specification + Repository,(c) 維持現狀不用模式。」

詳細模式指引,請讀 design-principles.md

關鍵行為

  • 別提設計不需要的模式。 模式上癮(pattern-itis)是真的。若不用 Strategy 也能滿足 SRP,就別引入 Strategy。
  • 解釋後果。 「在這裡加 Composite 代表⋯」——使用者得到什麼、付出什麼?
  • 承接使用者先前說過的話。 若使用者已決定把某關注點移到別處(例如「AutomationLevel 是排程關注點,不是資源關注點」),在審查中反映那點。

結束步驟 3

更新類別圖(若採用了模式)並呈現修訂版。然後問是否要:

  • (a) 進到步驟 4(程式碼骨架)
  • (b) 繼續疊代這張圖
  • (c) 產出文件(給 Claude Code 或團隊交接的 Markdown 檔)
  • (d) 到此為止——設計已足夠好,使用者可自行實作

步驟 4:程式碼骨架 + 測試 (選用,僅在要求時)

目標

產出帶代表性測試的 Java 介面與基底類別骨架,作為實作的起點。

開始前先確認:

  1. 程式碼風格(用 ask_user_input_v0):
    • 純 POJO(無框架)?Spring Boot?Java record?Lombok?
  2. 細節程度
    • 核心類別 + 介面 + 2-3 個代表性測試(輕量)?或
    • 含全部類別的完整可編譯骨架?
  3. 註解語言
    • 英文?繁體中文?混用(程式碼英文、註解中文)?

這三個答案形塑整份輸出。別假設——一律先問。

輸出結構

  1. Package 佈局 — 顯示提議的目錄樹
  2. 核心類別,依 package 分組、按依賴順序排列(shared → organization → domain → application)
  3. 測試類別 — 2-3 個涵蓋最棘手不變式的代表性測試

風格慣例

欄位宣告

除非另有要求,OOA 階段的骨架別產生 getter/setter。用註解標示欄位:

public class RecurringShift {
    // 欄位:
    //   String shiftId
    //   DayOfWeek dayOfWeek
    //   LocalTime startTime
    //   Duration duration
 
    public boolean isOvernight() { ... }
    public List<TimeSlot> materialize(LocalDate date) { ... }
}

這讓焦點放在結構與關係上,而非樣板程式。

方法體

三種風格,依脈絡選擇:

  1. Stub{ ... }):用於圖已傳達意圖的例行方法
  2. 虛擬碼註解:用於有微妙順序或不變式的方法
  3. 真實實作:用於展示核心設計理念的方法(例如某模式的主力方法)

測試

  • 預設用 JUnit 5
  • 測試名稱:英文 should... 形式
  • 測試內部:用使用者偏好語言寫註解
  • 一個測試對應一個不變式或邊界案例,而非對應一個方法

須遵循的常見樣式

output-templates.md 取得下列項目的標準範例:

  • Java 版的 Specification 模式
  • Repository 介面
  • 帶前置條件檢查的 Aggregate Root
  • 帶 factory method 的不可變 Value Object
  • 帶 template method 的繼承

結束步驟 4

提供後續路徑:

  • 把 stub 展開成完整實作
  • 加更多測試(指定哪些不變式)
  • 產出給 Claude Code 的 Markdown 文件
  • 進到 OOD 或實作階段