本章介紹了六大套件設計原則(Package Principles),分為兩組:三個處理內聚性(Cohesion)的原則決定「哪些類別該放在同一個元件中」,三個處理耦合性(Coupling)的原則決定「元件之間該如何相互依賴」。最後引入一系列衡量指標,將這些原則量化。

元件內聚性原則#

REP:再利用/發布等價原則(Reuse/Release Equivalence Principle)#

The granule of reuse is the granule of release.

  • 再利用的粒度就是發布的粒度:若希望某些類別能被其他人重用,它們就必須以正式發布流程來管理——有版本號、有追蹤系統、有完整的通知機制
  • 使用者需要「選擇要不要升級到新版」的自由,因此作者必須承諾向後相容,並且提供足夠的通知期
  • 推論:放在同一個元件中的類別必須是可以一起發布的,它們應共用同一套版本號與發布文件

重點: REP 原則意味著元件中的類別必須屬於一個可內聚重用的群組。不相關的類別不應放在同一個元件中——元件不是隨意將類別分桶的機制。

CCP:共同封閉原則(Common Closure Principle)#

The classes in a component should be closed together against the same kinds of changes. A change that affects a component affects all the classes in that component and no other components.

  • CCP 是 SRP 在元件層級的對應:正如 SRP 說「一個類別不應該有多於一個理由需要修改」,CCP 說「一個元件不應該有多於一個理由需要修改」
  • 將因相同原因而一起變化的類別聚集在同一個元件中,可以最小化發布、驗證與部署的工作量
  • 可維護性的優先級高於可重用性——對多數應用程式而言,程式碼容易修改比容易重用更加重要

技巧: CCP 要求我們把因相同原因改變的類別放在一起。這意味著如果某個業務規則變更只需改動一個元件,那麼這個設計就是成功的。

CRP:共同重用原則(Common Reuse Principle)#

The classes in a component are reused together. If you reuse one of the classes in a component, you reuse them all.

  • CRP 告訴我們哪些類別不該放在一起:如果元件中有些類別被使用,有些沒有,那麼未使用的類別變動時,使用者仍必須重新部署——這是不必要的耦合
  • 緊密合作的類別(如容器類與其迭代器)應放在同一個元件中
  • CRP 是 ISP 在元件層級的對應:ISP 說「不要依賴你不需要的方法」,CRP 說「不要依賴你不需要的類別」

注意: REP、CCP 與 CRP 三個原則之間存在張力。REP 與 CCP 傾向讓元件變大(把相關的類別聚在一起),CRP 則傾向讓元件變小(排除不被一起使用的類別)。架構師必須在三者之間取得平衡。

元件耦合性原則#

ADP:無環依賴原則(Acyclic Dependencies Principle)#

Allow no cycles in the component dependency graph.

  • 當多個開發者在同一個專案中工作,若缺乏管理策略,就會出現晨間症候群(morning-after syndrome)——昨天能用的程式碼,今早因別人的修改而壞掉
  • 兩種解決方案:
    • 每週建置(Weekly Build):開發者各自獨立工作,週五整合。隨著專案成長,整合時間會侵蝕開發時間
    • 消除依賴循環:將系統分割成可獨立發布的元件,每個團隊維護自己的元件並給予版本號

Figure 28.1: Component structures are a directed acyclic graph.

  • 元件依賴圖必須是有向無環圖(DAG):當 A 依賴 B 時,不能存在一條路徑讓 B 回頭依賴 A
  • 若出現循環依賴,可用兩種方式打破:

Figure 28.2: A component diagram with a cycle

Figure 28.3: Breaking the cycle with dependency inversion

  1. 依賴反轉原則(DIP):提取介面放在被依賴方的元件中
  2. 建立新元件:將兩個元件共同依賴的類別抽出,放入第三個元件

補充: 元件依賴圖不是在專案初期就能規劃好的——它會隨著系統成長與演化而改變。ADP 原則是在開發過程中持續監控並修正循環。

SDP:穩定依賴原則(Stable Dependencies Principle)#

Depend in the direction of stability.

  • 穩定性的定義:一個元件如果有很多其他元件依賴它,就很難修改——它是穩定的。反之,如果一個元件不被任何人依賴,它就容易修改——它是不穩定的
  • 穩定性並非「好」或「壞」——系統需要同時擁有穩定與不穩定的元件。關鍵是不穩定的元件不應被穩定的元件依賴

Figure 28.5: X: A stable component

  • 穩定度指標(Instability, I)
    • Ca(Afferent Coupling,傳入耦合):依賴這個元件中類別的外部類別數量
    • Ce(Efferent Coupling,傳出耦合):這個元件中依賴外部類別的類別數量
    • I = Ce / (Ca + Ce),範圍 [0, 1]
    • I = 0 表示最大穩定,I = 1 表示最大不穩定

Figure 28.7: Tabulating Ca, Ce, and I

Figure 28.8: Ideal component configuration

  • SDP 要求:元件的 I 值應沿著依賴方向遞減——每個元件的 I 值應小於它所依賴的元件的 I 值
  • 若發現穩定的元件(低 I)依賴了不穩定的元件(高 I),可使用 DIP 反轉依賴方向

Figure 28.11: Fixing the stability violation, using DIP

SAP:穩定抽象原則(Stable Abstractions Principle)#

A component should be as abstract as it is stable.

  • 穩定的元件既然難以修改,就應該透過抽象化來保持可擴展性:穩定的元件應包含大量抽象類別與介面,讓不穩定的元件去提供具體實作
  • 抽象度指標(Abstractness, A)
    • Na:元件中抽象類別與介面的數量
    • Nc:元件中類別的總數
    • A = Na / Nc,範圍 [0, 1]

主序列與距離指標#

  • 結合 I(不穩定度)與 A(抽象度),可在 A-I 圖上繪製每個元件的位置
  • 理想的元件應落在主序列(Main Sequence)上,即 A + I = 1 的直線上

Figure 28.13: Zones of exclusion

  • 兩個應避免的區域:
    • 痛苦區(Zone of Pain):A = 0, I = 0——既具體又穩定,修改極為困難。例如資料庫 schema 就常落在此區
    • 無用區(Zone of Uselessness):A = 1, I = 1——完全抽象卻無人依賴,是死碼

Figure 28.14: Scatter plot of component D scores

  • 距離指標(D, Distance from the Main Sequence)
    • D = |A + I - 1|,範圍 [0, ~0.707]
    • D 愈接近 0 表示元件愈接近主序列,是理想的設計
    • 可將 D 標準化為 D’ = |A + I - 1|,範圍 [0, 1]
    • 透過統計分析(平均值與標準差),可以找出偏離主序列過遠的異常元件

重點: 元件設計指標(I、A、D)是設計決策的輔助工具,而非絕對標準。它們幫助我們量化分析元件之間的依賴關係,找出可能有問題的設計,但最終的判斷仍需要架構師的專業經驗。

本章小結#

六大套件原則可總結如下:

原則層級關注重點
REP內聚性元件是可重用的發布單位
CCP內聚性因相同原因變化的類別放在一起
CRP內聚性不一起使用的類別不放在一起
ADP耦合性依賴圖不可有循環
SDP耦合性依賴方向朝向穩定
SAP耦合性穩定的元件應是抽象的

這些原則是 SOLID 原則在更高層級——元件與部署層級——的延伸,幫助架構師在可重用性、可維護性與穩定性之間做出合理的取捨。