當實作中的變化點未被封裝在抽象或階層內時,就出現 Missing Encapsulation 壞味道。

核心定義#

兩種典型表現:

  • 客戶端緊耦合於它所需服務的多個變體——每多支援一個變體或修改既有變體,客戶端都跟著痛。
  • 階層中為每一種「變化組合」都建一個類別——一旦新增維度,類別爆炸

此壞味道直接違反 Open Closed Principle (OCP):型別應「對擴充開放、對修改關閉」。

案例:ResourceBundle 的 OCP#

JDK 7 的 java.util.ResourceBundle 預設實作足以應付多數情境,但對於需要自訂搜尋策略、快取機制的應用,提供了 ResourceBundle.Control 作為擴充點

這是「simple things should be simple, complex things should be possible」(Alan Kay)的具體實踐——也是封裝原則中隱藏變化Variation Encapsulation Principle )的好例子。

常見成因#

  • 不察覺哪些關注點會變:缺乏 OCP 訓練、缺乏對產品 roadmap 的判斷。
  • 缺乏重構:需求變動時未即時調整設計。
  • 混合關注點:多個正交(orthogonal)關注點塞進同一個階層。
  • 天真的設計決策:「每個變化組合都建一個類別」——典型的反樣式。

範例#

範例 1:Encryption 類別硬塞多種演算法#

Figure 4.9: Encryption 類別最初僅提供 DES 演算法

最初 Encryption 類別只支援 DES。為了支援 AES,新手把 encryptUsingDES()encryptUsingAES() 都加進 Encryption

後果:

  • Encryption 類別越來越大、越難維護。
  • 新加演算法必須改 Encryption 本身——違反 OCP。
  • 演算法被鎖死在 Encryption 內,無法在其他脈絡中重用

Figure 4.10: Encryption 類別同時提供 DES 與 AES

範例 2:內容 × 演算法的類別爆炸#

設計支援多種內容(image、text)× 多種演算法(DES、AES)的加密——若為每個組合建一個類別DESImageEncryptionAESTextEncryption…),新增 TDES 與 Data 內容類別時:

Figure 4.11: 支援多種內容 × 演算法加密的階層

flowchart TD
    A[Encryption] --> B[DESImage]
    A --> C[DESText]
    A --> D[AESImage]
    A --> E[AESText]
    A --> F[TDESImage]
    A --> G[TDESText]
    A --> H[DESData]
    A --> I[AESData]
    A --> J[TDESData]

兩個變化點 × N 種選項 = N² 個類別。維度多了就是組合爆炸。

Figure 4.12: Encryption 階層中的類別爆炸

建議重構#

通用解:封裝變化。常用樣式:Strategy Bridge

範例 1:套用 Strategy 樣式#

  • 建立 EncryptionAlgorithm 介面。
  • DESEncryptionAlgorithmAESEncryptionAlgorithm 各實作其演算法。
  • Encryption 類別持有一個 EncryptionAlgorithm 參考

優點:

  • 執行期可切換演算法。
  • 演算法可在其他脈絡中重用。
  • 新增演算法不必動 Encryption

Figure 4.15: 套用 Strategy 樣式的建議重構

書中也分析過用「繼承」做的兩種錯解——其一造成 Broken Hierarchy(6.8 節),其二讓子類別繼承不必要的方法、降低重用性。

Figure 4.13: 重構選項一(Broken Hierarchy)

Figure 4.14: 重構選項二(使用繼承)

範例 2:套用 Bridge 樣式#

把「內容類型」與「演算法類型」拆成兩個獨立的階層,由 EncryptionContentType 階層持有 EncryptionAlgorithm 介面參考。

Figure 4.16: 支援多種內容與演算法的建議重構

新增 TDES 與 Data 時:原本要加 5 個類別(爆炸版),重構後只需 2 個類別

對品質屬性的衝擊#

  • 理解性:抽象內塞了多種服務 → 變複雜;類別爆炸 → 整體結構難理解。
  • 可變更性與可擴充性:客戶端緊耦合到所有變體。
  • 可重用性:服務嵌在抽象中無法獨立使用;正交關注點混雜時更難拆出可用的單元。

別名#

  • Nested generalization:階層第一層做了一個泛化,後續層則「乘出」所有組合。
  • Class explosion:因多重泛化導致類別爆炸。
  • Combinatorial explosion:每新增一族變體就要再加一群類別。