有一個類別「什麼都連」:一大票類別呼叫它(高 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)。
- 常見成因:責任指派不當(一個類別攬太多)、或**「萬用工具類」**(
XxxUtils、XxxHelper收容各種雜事)。 - 別名:Bottleneck(大量互相引用)、Local hub、Man-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
- Hub-like = fan-in 與 fan-out 雙向都高的集線器類別,一改牽動整片,且常以執行期問題浮現。
- 成因多是責任亂塞或萬用工具類;解法是 SRP 拆責任、move method 讓成員歸位、必要時用 Chain of Responsibility 降 fan-in。
- 例外是天生的核心抽象(如
java.lang.Class)——那是合理權衡,別為指標硬拆。
No notes yet — jot your takeaways or Q&A here.