一個什麼都連的 hub 類別,怎麼辨識、怎麼拆?

Medium★★★★Studied

FromRefactoring for Software Design Smells

Concepts practiced📏 單一職責原則 (SRP)📏 迪米特法則 (Law of Demeter)🔁 責任鏈 (Chain of Responsibility)

Convention: read the requirements & constraints first, sketch your own design, then expand the reference to compare.

📚 From the Books

一個什麼都連的 hub 類別,怎麼辨識、怎麼拆?ProblemMedium反模式辨識

有一個類別「什麼都連」:一大票類別呼叫它(高 fan-in),它自己又反過來引用一大票類別(高 fan-out)。它成了設計裡的集線器(hub),一改就牽動外圍幾百個類別。怎麼辨識、怎麼拆?

🎯 Scenario

// UiUtils 被 swing 般的眾多類別呼叫(高 fan-in),
// 自己又反向引用一堆具體 UI 型別(高 fan-out)——雙向都爆
object UiUtils {
    fun layout(window: Window, panel: Panel, dialog: Dialog) { /* ... */ }
    fun paint(canvas: Canvas, image: Image, font: Font) { /* ... */ }
    fun measure(text: Text, font: Font): Rect { /* ... */ }
    // …被 40 個類別呼叫、自己認得 34 種型別
}

壞味道:Hub-like Modularization——抽象的 fan-in 與 fan-out 同時很高。違反 Meyer 的 “few interfaces” 規則:「每個模組應與盡可能少的其他模組溝通。」書中真實案例 java.awt.Component 的 fan-in 高達 498、fan-out 71,成了系統的瓶頸。

🧠 Intuition

TIP

先別看下面。數兩個方向:有多少類別依賴它(fan-in)?它依賴多少類別(fan-out)? 兩個數字都大,它就是 hub。再問:這些進出相依,是因為它扛了太多不相關的責任嗎?

⚖️ Reference Design

辨識訊號與別名
  • 雙向都高:既被大量呼叫,又反向引用大量型別(單向高只是普通熱點,不是 hub)。
  • 常見成因:責任指派不當(一個類別攬太多)、或**「萬用工具類」**(XxxUtilsXxxHelper 收容各種雜事)。
  • 別名:Bottleneck(大量互相引用)、Local hubMan-in-the-middle(像中介夾在眾多模組間)。
解法:拆責任、讓成員歸位、必要時接力通知
// 1) 成員歸位(move method):glyph 知識屬於 GlyphVector,不屬於 Font
class GlyphVector(text: Text, font: Font) { /* 原本塞在 Font 的邏輯搬來這 */ }

// 2) 責任拆分(split / extract class):把 UiUtils 沿主題切開
object LayoutOps { /* 只管排版 */ }
object PaintOps  { /* 只管繪製 */ }
  • 責任拆分:若責任過多,把它們分散到新/舊抽象,進出相依自然縮減(呼應 SRP)。
  • 成員歸位:把擺錯位置的方法 move 到概念上該屬於的類別(如 glyph 相關搬到 GlyphVector),降 fan-out。
  • 接力通知降 fan-in:當大量接收者都對 hub 的同一份資料感興趣,改用 Chain of Responsibility——hub 只通知第一位,後續接力傳遞。
  • 少讓客戶端「穿過 hub 拿東西再操作」也符合迪米特法則
務實例外:核心抽象
  • 大型 OO 應用必有少數核心抽象天生是樞紐。例如 java.lang.Class 的 fan-in 超過 1000。
  • 對這類核心抽象,控制 fan-in/fan-out 往往極其困難甚至不可能——這是合理的設計權衡,不必硬拆。先判斷你的 hub 是「責任亂塞」還是「天生核心」。

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading

🔗 Dive Deeper