當一個抽象同時對大量其他抽象有進、出兩個方向的相依時,就出現 Hub-like Modularization——它變成設計中的「集線器」。
核心定義#
模組化的基礎是「高內聚、低耦合」。Meyer 的「few interfaces」規則更進一步指出:
「每個模組應與盡可能少的其他模組溝通。」—— Bertrand Meyer
當抽象的 fan-in 與 fan-out 都很高時,相依結構就像個 hub。
常見成因#
- 責任指派不當:類別承擔太多責任,吸引大量進出相依。
- 「萬用工具類」:例如
javax.swing.SwingUtilities,被 swing 套件的諸多類別呼叫,本身又反向引用大量 UI 類別——成為標準的 hub。
範例#
範例 1:java.awt.Font#
java.awt.Font 表達字型,並支援把文字渲染為一連串字符(glyph):
- fan-in:19
- fan-out:34

Figure 5.25: java.awt.Font 的進入與外送相依
範例 2:java.awt.Component#
Component 抽象所有可顯示在螢幕上的圖形物件:
- fan-in:498
- fan-out:71

Figure 5.26: java.awt.Component 的進入與外送相依
這類 hub 一旦改動,外圍幾百個類別跟著動搖。
建議重構#
- 責任拆分:若責任過多,把責任分散到新/舊抽象,自然減少進出相依。
- 成員歸位:將擺錯位置的成員 move 到合適的類別。
- Chain of Responsibility 樣式:當大量接收者對 hub 中的同一資料感興趣時,將接收者連成 Chain of Responsibility ↗,由 hub 通知第一位即可,後續接力傳遞——大幅降低 fan-in。
範例 1 的重構#
Font 中的 createGlyphVector() 直接知道 GlyphVector:
public GlyphVector createGlyphVector(FontRenderContext frc, CharacterIterator ci) {
return new StandardGlyphVector(this, ci, frc);
}字型與 glyph 概念上相關,但 glyph 相關功能應該放在
GlyphVector。Font不必持有 glyph 知識,相依數可順勢縮減。
範例 2 的重構#
對 Component 套用 move method、split class、extract class、extract method。範例:
FontMetrics getFontMetrics(Font font);
Image createImage(ImageProducer producer);
Image createImage(int width, int height);
VolatileImage createVolatileImage(int width, int height);
VolatileImage createVolatileImage(int width, int height, ImageCapabilities caps);把這些方法移到 FontMetrics、Image、VolatileImage,相依關係跟著轉移。
對品質屬性的衝擊#
- 理解性:要理解 hub 必須一起理解它依賴的眾多類別。
- 可變更性、可擴充性、可靠性:hub 一改,連鎖效應遍及整個設計,且常以執行期問題形式呈現。
- 可重用性與可測試性:hub 的 fan-out 太大,無法獨立解耦使用或測試。
別名#
- Bottlenecks:類別大量引用其他類別、也被大量引用。
- Local hubs:型別有大量直接相依與被相依。
- Man-in-the-middle:類別像中介者,連接眾多類別/模組。
實務考量:核心抽象#
大型 OO 應用必有少數核心抽象扮演關鍵角色。例如
java.lang.Class:JDK 中所有類別/介面的中樞,fan-in 超過 1000、fan-out 約 40。 雖然嚴格定義下這也是 Hub-like,但對核心抽象來說,控制 fan-in/fan-out 通常極其困難(甚至不可能)——這是設計上的合理權衡。