前章討論的是「如何將類別歸類到元件(內聚性)」,
這章討論的是「元件之間如何互動(耦合性)」。

這裡有三個核心原則,它們共同決定系統架構的依賴關係圖是否健康。

Figure 14.1: Typical component diagram

一、ADP: 無環依賴原則#

(The Acyclic Dependencies Principle)

「元件依賴圖中不該出現環(Cycle)。」

1. 宿醉症候群 (Morning After Syndrome)#

你有過這種經驗嗎?昨天程式跑得好好的,今天回來卻編譯失敗,因為隔壁團隊修改了他們負責的元件。
當系統出現 循環依賴(A → B → C → A) 時,這種問題會變成惡夢。

這三元件其實變成一個巨大的「超級元件」,牽一髮動全身,導致無法獨立發佈。

flowchart LR
    A[元件 A] --> B[元件 B]
    B --> C[元件 C]
    C --> A

    style A fill:#f8d7da
    style B fill:#f8d7da
    style C fill:#f8d7da

Figure 14.2: A dependency cycle

2. 解決方案:有向無環圖 (DAG)#

架構師的目標是確保依賴關係始終是個 有向無環圖 (Directed Acyclic Graph)
如果發現環,有兩種解法:

  1. DIP (依賴反向): 建立介面,反轉依賴方向。

    Figure 14.3: Inverting the dependency

  2. 新元件: 將造成循環的共同依賴類別抽離,放入一個新元件中。

    Figure 14.4: The new component

元件結構不是在專案第一天就設計好的(Top-down),而是隨著系統發展而演進
隨著功能增加,環會不斷出現,架構師必須隨時監控並消除它們。

二、SDP: 穩定依賴原則#

(The Stable Dependencies Principle)

「依賴關係須指向更穩定的方向。」

1. 穩定性 (Stability) 的定義#

在軟體中,穩定不代表「不變」,而是代表 「難改變」

  • X 依賴於 Y: 如果 X 改了,Y 不用動。但如果 Y 改了,X 就得跟著動
  • 結論: 被依賴者(Y)比依賴者(X)更需負責任,因此更難改變(更穩定)

    Figure 14.5: X: a stable component

    Figure 14.6: Y: a very unstable component

2. 穩定性公式 (I 指標)#

Uncle Bob 提出一個量化公式: $$I = \frac{FanOut}{FanIn + FanOut}$$

  • Fan-in (輸入依賴): 有多少外部類別依賴我?(越多越穩定)
  • Fan-out (輸出依賴): 我依賴多少外部類別?
  • I (不穩定性 Instability):
    • I = 0:最穩定(只有人依賴我,我不依賴人)。負責者
    • I = 1:最不穩定(我不被依賴,但我依賴一堆人)。不負責者

SDP 法則: 依賴箭頭指向的元件,其 I 須小於發起依賴的元件。(I 值應隨依賴方向遞減)。 如果一個 I=1(易變)的元件被一個 I=0(難改)的元件依賴,這就是架構設計錯誤。

Figure 14.8: An ideal configuration

Figure 14.9: SDP violation

Figure 14.10: U within Stable uses C within Flexible

Figure 14.11: C implements the interface class US

三、SAP: 穩定抽象原則#

(The Stable Abstractions Principle)

「一個元件的抽象度,應與其穩定度一致。」

如果一個元件非常穩定(I=0,很難改),我們希望它至少要很靈活(易於擴展)。

  • 如何做到難改但靈活? 答案是 OCP(開放封閉原則)
  • 實作方式:抽象類別(Abstract Classes)介面(Interfaces)

1. 抽象性公式 (A 指標)#

$$A = \frac{N_a}{N_c}$$

  • N_a:元件中抽象類別/介面的數量
  • N_c:元件中類別的總數
  • A = 0:完全具體
  • A = 1:完全抽象

2. 主序列 (The Main Sequence)#

將 I(不穩定性)作為 X 軸,A(抽象性)作為 Y 軸,我們可以畫出一張架構象限圖。

Figure 14.12: The I/A graph

  • 痛苦地帶 (Zone of Pain) - (0,0):
    • 高度穩定 + 高度具體
    • 很難改(被大家依賴),又無法擴展(不是介面)
    • 典型例子: 資料庫 Schema、具體 Utility 函式庫
  • 無用地帶 (Zone of Uselessness) - (1,1):
    • 高度不穩定 + 高度抽象
    • 沒人依賴它,它卻弄了一堆抽象介面。這是過度設計(Over-engineering)
  • 主序列 (Main Sequence):
    • 連接 (1,0) 到 (0,1) 的對角線。
    • 理想落點: 好元件應落在這條線附近
    • 意義: 越穩定的元件應越抽象(左上角);越不穩定的元件應越具體(右下角)

Figure 14.13: Zones of exclusion

quadrantChart
  title Stability-Abstractness Quadrant
  x-axis Stable --> Unstable
  y-axis Concrete --> Abstract
  quadrant-1 Zone of Uselessness
  quadrant-2 Ideal Zone
  quadrant-3 Zone of Pain
  quadrant-4 Ideal Zone

Figure 14.14: Scatterplot of the components

D (距離) = | A + I - 1 | 架構師的目標是讓所有元件的 D 值趨近於 0(落在主序列上)。
這提供了量化架構品質的數學依據。

Figure 14.15: Plot of D for a single component over time