當一個抽象只提供互補方法的其中一邊——例如有 initialize() 卻沒 dispose()、有 add() 卻沒 remove()——就出現 Incomplete Abstraction 壞味道。

核心定義#

抽象的使能技巧之一是「讓抽象連貫且完整」。當互補或彼此關聯的方法只支援一半時:

  • 客戶端被迫自行補實作,常常為此直接存取內部資料——封裝跟著被破壞。
  • 一半的責任被推到別的地方實作,內聚降低,違反 SRP。

常見成因#

  • 缺乏整體視角:開發者只專注於眼前需求,忽略一組方法是否被完整地一起提供。
  • 不遵守語言/函式庫慣例
    • Java 的 HashMap 依賴 hashCode()equals() 一起被覆寫;只覆寫一個會導致檢索異常。
    • C++ 的「rule of three」:要嘛全部定義 default constructor、copy constructor、assignment operator、destructor(多型情境下需 virtual destructor),要嘛全都不定義。

範例#

範例 1:JDK 的 ButtonModel#

javax.swing.ButtonModel 提供了 setGroup(),文件說明「識別 button 所屬的群組」,但沒有對稱的 getGroup()

範例 2:Abstract Factory 樣式#

Abstract Factory pattern 的標準描述只定義「建立物件的職責」,未明確包含「刪除物件」。在不支援垃圾回收的語言中,套用此樣式而忽略物件刪除,就會產生 Incomplete Abstraction。

Figure 3.9: Abstract Factory 樣式的類別圖

軼事:架構師假設「自己懂就好」#

一個智慧裝置設定工具發布前出現嚴重記憶體洩漏。專家追查發現:架構師的設計文件只列出「建立」方法,預期開發者會「自己理解」要對應建立刪除方法。開發者卻被要求嚴格遵守設計文件,因而完全沒處理刪除——洩漏因而蔓延整個程式碼。

教訓:

  • 設計文件必須明確——不能依賴「對方應該懂」。
  • 團隊文化要鼓勵開發者對架構提出回饋;Agile 倡導的「集體擁有設計」可以避免這類盲點。

建議重構#

為缺失的對稱/互補方法直接補上。但若是已公開的 API,要小心衝擊既有客戶端。

書中常見對稱方法配對舉例:

動作對應
openclose
startstop
addremove
pushpop(Stack)
sourcesink(Stream)
initializedispose
enabledisable
showhide
  • 範例 1:理想上應在 ButtonModel 介面補上 getGroup();但因為 JDK 是公開 API,貿然在介面新增方法會破壞所有實作者。最終 JDK 1.3 在子類別 DefaultButtonModel 中補上 getGroup(),是個折衷。

Figure 3.10: JDK 1.3 中 ButtonModel 介面的重構

  • 範例 2:在 AbstractFactory 中明確加入物件刪除責任;或為 Factory Method 樣式採用 Factory Disposal Method 樣式。

介面演化困難——這正是設計 API 時要特別警惕 Incomplete Abstraction 的原因。

對品質屬性的衝擊#

  • 理解性:使用者難以理解「為什麼少了那個對稱方法」。
  • 可變更性與可擴充性:互補方法分散在多處時,每次變更都要追溯多個抽象。
  • 可重用性:被迫在新脈絡裡補實作不支援的部分。
  • 可靠性:客戶端為補洞而直接存取內部資料,極易引發執行期錯誤——例如有 lock 卻沒 unlock 時的 workaround。

別名#

  • Class supports incomplete behavior:類別的公開介面不完整。
  • Half-hearted operations:互補方法以不完整或不一致的方式提供。

實務考量#

刻意不提供對稱方法#

唯讀集合可能只提供 add() 而沒有 remove()(甚至兩者都沒有)。有意識的設計選擇不算壞味道。

用一個帶布林參數的方法取代成對方法#

java.awt.MenuItemjava.awt.Component 早期分別有 disable()enable(),後來被 setEnabled(boolean) 取代;同理 show()hide()setVisible(boolean) 取代。

由於這些方法以布林參數取代成對的動作,沒有對應的 getEnabled()getVisible() 是合理的——不算 Incomplete Abstraction。