當一個動詞型操作被硬包成類別——尤其是只有一個方法、且資料還在別的類別裡——就出現 Imperative Abstraction 壞味道。
核心定義#
物件導向的根本是把問題領域中的「東西」(things)建模為抽象。但當設計者把「動作」(operations)擴張為類別時,會產生:
- 類別名稱本身像動詞(
Read、CreateReport、Diff、OffsetCalculator)。 - 內部只定義一個方法,方法名常與類別名相同。
- 操作的資料位於別的類別——典型的「行為與資料分離」。
這是把結構化程式設計(structured programming)穿上 OO 外衣,而不是真正的 OO。
連帶副作用:
- 類別大爆炸,提高設計複雜度。
- 同一份資料的相關方法被切散,內聚降低——也違反封裝與模組化原則。
常見成因#
程序式思維(procedural thinking):來自 C/其他程序式背景的開發者轉用 OO 語言時,常把資料對應為「只含 public 欄位的資料類別」,操作則放到另一群類別中——本質上是 C 風格的 struct + 獨立函式的翻版。
範例#
範例 1:報表系統的類別爆炸#
財務系統中為了報表功能,建立 CreateReport、CopyReport、DisplayReport 等類別,各只含一個方法。資料則放在 Report 這個「資料類別」中。

Figure 3.5: 報表生成功能的類別圖
把本應內聚於
Report的所有方法強行散落到三個類別,只是換個地方寫程序式程式碼。
範例 2:影像處理的 OffsetCalculator/SaveHandler#
Application 用 OffsetCalculator.calculateOffset() 計算兩張影像的偏移,再把結果交給 SaveHandler.saveOffset() 儲存。兩個類別都只有單一方法,是 Imperative Abstraction。

Figure 3.6: 影像處理應用程式的類別圖
建議重構#
找出(或建立)一個合適的「東西」抽象,把方法和它需要的資料一起放進去。
- 範例 1:把
CreateReport等類別中的方法搬回Report類別。

Figure 3.7: 報表生成功能的建議重構
- 範例 2:建立
ImagePair類別,封裝「offset 的計算與儲存」這組行為。

Figure 3.8: 影像處理應用程式的建議重構
範例 2 的
ImagePair是否違反 SRP?實際的 I/O(開檔、連 DB)會被交給另一個類別處理;saveOffset()僅負責呼叫——責任仍是單一的。
對品質屬性的衝擊#
- 理解性:抽象與問題領域沒有直接對應;類別爆炸提高整體複雜度。
- 可變更性與可擴充性:對單一概念實體的修改要動到多個類別。
- 可重用性:要重用報表功能,必須一起搬走
CreateReport、CopyReport、DisplayReport、Report四個類別並調整。 - 可測試性:多個單方法類別必須集體測試才能覆蓋一個概念。
軼事:函式分解 vs. 物件分解#
一個 Java 工具包專案在發布前的設計審查中發現
Diff類別只含findDiff()方法。深入檢視後,發現整個專案幾乎所有類別都以動詞命名(generate、report、process、parse)——這是整個專案採用了「函式分解」而非「物件分解」!
幾個關鍵教訓:
- 即使是熟悉專案的成員,也容易忽略明顯的設計問題——定期由外部專家做設計審查很有幫助。
- 一個壞味道往往是更深問題的徵兆——這個案例的根因是「程序式思維」。
- 越接近發布,重構成本越高、風險越大。早期就要警覺。
別名#
- Operation class:本應為類別中之方法,卻被獨立成類別。
實務考量:物化(reification)#
把「非物件」提升為「物件」叫做物化。這在某些設計樣式中是有意為之的:
- State pattern:將狀態機編碼為物件。
- Command pattern:將請求物化為命令物件。
- Strategy pattern:將參數化的演算法封裝為物件。
當物化是為了取得靈活性、可重用性、可擴充性而被刻意設計時,就不算壞味道。