當一個抽象同時對大量其他抽象有進、出兩個方向的相依時,就出現 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 相關功能應該放在 GlyphVectorFont 不必持有 glyph 知識,相依數可順勢縮減。

範例 2 的重構#

Component 套用 move methodsplit classextract classextract 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);

把這些方法移到 FontMetricsImageVolatileImage,相依關係跟著轉移。

對品質屬性的衝擊#

  • 理解性:要理解 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 通常極其困難(甚至不可能)——這是設計上的合理權衡。