如果說 SOLID 管的是「類別與函式」的層級,元件原則管的就是「可部署單位」的層級。元件是部署的最小單位——Java 的
.jar、Ruby 的 Gem、.NET 的.dll。設計得當的元件應能獨立部署、獨立開發。這一層有六條原則,分成兩組:三條談「哪些類別該放在同一個元件裡」(內聚),三條談「元件之間該如何依賴」(耦合)。
🧠 Core Ideas
- REP(重用/發佈等價):重用的粒度就是發佈的粒度。想讓別人重用你的元件,就必須做版本管理——沒有版本號與變更紀錄,重用是不可能的。
- CCP(共同封閉):把「會因相同理由、在相同時間改變」的類別放在同一元件。這是元件層級的 SRP,服務的是可維護性——需求變更時只需重新部署這一個元件。
- CRP(共同重用):不要強迫使用者依賴他們不需要的東西。這是元件層級的 ISP——只有「幾乎總是一起被用」的類別才該放在一起。
- 三者相互制衡:REP 與 CCP 是包容性(讓元件變大),CRP 是排除性(讓元件變小)。架構師的工作就是在這個「張力三角」裡找動態平衡。
- ADP(無環依賴):元件依賴圖中不該有環(Cycle)。循環依賴會讓多個元件退化成一個無法獨立發佈的「超級元件」。
- SDP(穩定依賴)+ SAP(穩定抽象):依賴要指向更穩定的方向;而越穩定的元件應越抽象——穩定與抽象必須同步,才能既難改又靈活。
TIP
內聚三原則的落點會隨專案階段移動。專案初期「可發展性」最重要,重心偏向 CCP+CRP(在乎改得快,不急著被別人重用);專案後期其他團隊開始使用你的元件,重心才向 REP 移動(需要穩定的發佈管理)。好架構師不死守某一條原則——架構是關於時間與情境的動態平衡。
⚖️ 張力三角:顧此必然失彼
REP、CCP、CRP 三條內聚原則不是和諧共存,而是彼此拉扯。任何兩條被滿足,第三條就會被犧牲。
內聚三原則的取捨後果
-
REP + CCP(犧牲 CRP):元件太大,使用者被迫依賴一堆用不到的東西,帶來太多不必要的發佈。
-
CCP + CRP(犧牲 REP):元件太針對特定專案、缺乏版本管理,難以被其他團隊重用。
-
REP + CRP(犧牲 CCP):元件切得太細,一個簡單的需求變更卻要同時修改多個元件、到處救火。
-
啟示:三角形沒有「正確答案」,只有「當前最適落點」。初期過度追求 REP(瘋狂切分+版本管理)會讓開發慢到死;後期還死守 CCP(把所有東西塞一起)則讓維護與重用變惡夢。
🖼️ 用數學丈量架構品質:主序列
耦合三原則裡最精采的,是 Uncle Bob 把「架構好不好」變成了可以計算的數字。他定義了兩個指標:不穩定性 I = FanOut /(FanIn + FanOut),衡量一個元件被依賴(穩定)還是依賴別人(不穩定);抽象性 A = 抽象類別數 / 總類別數。這裡的「穩定」不是「不變」,而是「難改變」——被越多人依賴的元件,改動它要付的代價越大,因此越難改、越穩定。
把 I 當 X 軸、A 當 Y 軸畫出象限,兩個角落是危險地帶:(0,0) 是「痛苦地帶」——高度穩定又高度具體,被大家依賴卻無法擴展,典型例子是資料庫 Schema 與具體 Utility 函式庫;(1,1) 是「無用地帶」——沒人依賴卻堆滿抽象介面,是過度設計。理想的元件應落在連接 (1,0) 與 (0,1) 的對角線「主序列」附近:越穩定就該越抽象(靠 OCP、用介面實現「難改但靈活」),越不穩定就該越具體。架構師的目標是讓每個元件到主序列的距離 D = |A + I − 1| 趨近於 0——這給了「架構品質」一個量化的數學依據。
IMPORTANT
元件結構不是專案第一天就設計好的,而是隨系統發展而演進。隨著功能增加,循環依賴會不斷冒出來,架構師必須持續監控並用 DIP(建介面反轉依賴)或抽出新元件來消除它。把類別層級的 SOLID 放大到可部署單位——這就是 SRP 變成 CCP、ISP 變成 CRP、DIP 變成打破環的工具的地方。
🔑 Takeaways
- 元件是部署的最小單位;內聚三原則(REP/CCP/CRP)決定「誰和誰放一起」。
- CCP 是元件層 SRP、CRP 是元件層 ISP;三者彼此制衡,落點隨專案階段移動。
- 耦合三原則要求依賴無環(ADP)、指向更穩定(SDP)、且穩定即抽象(SAP)。
- 用 I / A 指標與主序列量化架構品質,讓「好架構」變得可測量。
- 延伸:打破循環依賴、指向穩定抽象,靠的正是類別層的那條依賴規則,見 依賴規則與邊界:細節指向策略。
No notes yet — jot your takeaways or Q&A here.