我們該如何決定哪些類別(Classes)該被放在同個元件(.jar/.dll)裡?
過去可能憑感覺,把「功能相似」的放在一起。
但 Uncle Bob 提出,元件內聚性取決於三個原則,其核心精神是:「別依賴你不需要的東西。」
這三原則彼此間存在著強烈張力(Tension),架構師的工作就是在這三者間尋找動態平衡。
一、三大原則#
1. REP: 重用/發佈等價原則#
(The Reuse/Release Equivalence Principle)
「重用的粒度(Granularity)就是發佈的粒度。」
- 核心: 如果希望別人重用你的元件,你須對該元件進行 發佈(Release) 管理
- 理由: 使用者需知道版本號(如 v1.0, v1.1),才能放心重用。
沒有版本號和變更紀錄,重用是不可能的 - 結論: 被放在同個元件裡的類別,應具有相同的發佈策略與版本號
2. CCP: 共同封閉原則#
(The Common Closure Principle)
「將那些會因相同理由、在相同時間改變的類別,放在同個元件中。」
- 對應原則: 這是元件層級的 SRP (單一職責原則)
- 理由: 可維護性(Maintainability)。如果兩個類別總是同時修改
(例如改了資料庫schema就得改對應Mapper),它們就該綁一起。
這樣需求變更時,我們只需重新部署這個元件,而不用到處救火 - 傾向: 讓元件變大(包容性)
3. CRP: 共同重用原則#
(The Common Reuse Principle)
「不要強迫使用者依賴他們不需要的東西。」
- 對應原則: 這是元件層級的 ISP (介面隔離原則)
- 理由: 如果元件 A 依賴元件 B,即使只用了 B 中的一個類別,A 也會在物理上依賴 B 的所有東西。
如果 B 裡面一個 A 沒用到的類別改了,A 還是得重新編譯/部署 - 結論: 只有那些「幾乎總是同時被用」的類別,才該放在一起
- 傾向: 讓元件變小(排除性)
二、張力圖:架構師的平衡藝術#
這三原則不是和諧共存,它們彼此制衡:
- REP & CCP 是包容性原則:傾向將類別放一起(讓元件變大)
- CRP 是排除性原則:傾向將類別拆開(讓元件變小)
Uncle Bob 畫了一個著名的「張力三角」:
| 組合 | 忽略的原則 | 導致的問題 |
|---|---|---|
| REP + CCP | CRP | 太多不必要發佈;元件太大,被迫依賴沒用東西 |
| CCP + CRP | REP | 難以重用;元件太針對特定專案,缺乏版本管理 |
| REP + CRP | CCP | 變更頻繁;簡單的需求變更需同時修改多個元件 |

三、結論:動態的平衡#
架構師的職責,就是根據專案當前狀態,在三角形中找到落點。
- 專案初期: 可發展性(Developability) 最重要
- 我們更在乎修改容易(CCP),而不是被別人重用(REP)
- 重心偏向三角形右側(CCP + CRP)
- 專案後期: 可重用性(Reusability) 變重要
- 隨著專案成熟,其他團隊開始使用你的元件,你需要穩定的發佈管理
- 重心逐漸向左側移動(REP)
好架構師不會死守某個原則。
如果你在專案初期就過度追求 REP(瘋狂切分元件並做版本管理),開發速度會慢到死;
如果你在專案後期還死守 CCP(把所有東西塞在一起),維護和重用將成為惡夢。
架構是關於時間與情境的動態平衡。