當兩個或多個抽象有相同名稱、相同實作、或兩者皆相同時,就出現 Duplicate Abstraction 壞味道。
核心定義#
兩種主要形態(也常合併出現):
- 同名(identical name):兩個抽象名稱相同,但行為可能不相關。仍會傷害理解性;若行為也相同,問題更嚴重。
- 同實作(identical implementation):成員定義語意上完全相同;但這份共通性沒有被抽出來。即使方法簽章不同也算數。
- 若這種重複出現在繼承的兄弟類別之間,往往同時觸發 6.3 節的 Unfactored Hierarchy。
此壞味道直接違反 DRY ↗(Don’t Repeat Yourself)原則:每一份知識在系統中應該有單一且明確的表示。
程式碼複製(code clones)的四種類型#
| 類型 | 性質 |
|---|---|
| Type 1 | 文字幾乎相同(僅空白、排版、註解差異) |
| Type 2 | 語法相同(變數名、空白、排版、註解差異) |
| Type 3 | 結構相同,但有少數陳述被加/改/刪 |
| Type 4 | 語意相同,但實作方式不同 |
常見成因#
- 複製貼上式編程:「先把功能做出來」的捷徑。
- 臨時性維護:多年來的隨意修補留下「cruft」(程式碼基底中的雜質)。
- 缺乏溝通:團隊成員不知道既有類別/方法,於是重新發明輪子。
- 類別被宣告為不可繼承:例如 Java 中的
final,迫使開發者整個複製該類別來做變體。
範例#
範例 1:JDK 中的 Timer 三胞胎#
JDK 中有三個 Timer:
javax.swing.Timer:在指定間隔執行ActionEvent。java.util.Timer:將執行緒排程在未來執行(背景執行緒)。javax.management.timer.Timer:發送警報通知。
三者描述都類似,新手開發者很難快速選對。
範例 2:Date 的繼承同名#
java.sql.Date 繼承自 java.util.Date,且兩者同名!編譯器因為它們在不同套件而不報錯,但在同一檔案中匯入兩者會出現名稱模糊,必須手動以全限定名稱解決。

Figure 3.13: JDK 中同名為 Date 的類別
範例 3:Eclipse 的 Scanner / PublicScanner#
org.eclipse.jdt.internal.compiler.parser.Scanner 與 org.eclipse.jdt.core.compiler.PublicScanner 兩個類別約 3500 行幾乎完全相同。JavaDoc 直接寫明:「mirrored in public package」。
軼事:MissingResourceException 的雙胞胎#
一個 Java 專案處理第三方函式庫拋出的
MissingResourceException,已加上 catch handler 仍偶爾崩潰。深查才發現:第三方函式庫自定了一個同名的MissingResourceException——catch抓的是java.util.MissingResourceException,真正拋出的是另一個。 因為兩個都是RuntimeException,編譯器不會警告。
教訓:
- 設計問題會引發難以追蹤的 bug。
- 命名很重要——避免重名尤其關鍵,特別是設計 API 時。
建議重構#
同名 → 改名讓兩者唯一。 同實作 → 視重複比例決定:抽出共通基底類別、抽出共通元件、或直接刪除其中一個。
- 範例 1:將
javax.swing.Timer改為ExecutionTimer/EventTimer,將javax.management.timer.Timer改為AlarmTimer,保留java.util.Timer。 - 範例 2:將繼承改為委派——讓
java.sql.Date包裹java.util.Date,並改名為java.sql.SQLDate。詳見 6.8 節 Broken Hierarchy。

Figure 3.14: java.util.Date 與 java.sql.Date 的建議重構
- 範例 3:重複度高達 99.9%,最佳解是刪除其中一個並讓另一個與其客戶端相容,而不是套繼承。
對品質屬性的衝擊#
- 理解性:客戶端會困惑「該用哪一個」;重複實作膨脹程式碼。
- 可變更性與可擴充性:一處改完還要同步改所有副本。
- 可重用性:Type 3/Type 4 的 clone 通常嵌入了脈絡特定的邏輯,難以遷移。
- 可靠性:型別誤用導致執行期錯誤;同步修改遺漏導致 bug。
別名#
- Alternative classes with different interfaces:做類似事但介面不同的類別。
- Duplicate design artifacts:等價的設計工件被複製多份。
實務考量#
容納變化#
java.util.Vector與java.util.ArrayList介面相似,但前者執行緒安全、後者不安全——這是有意分離兩個變體,不算壞味道。
不同情境中的同名型別#
DDD(Domain-Driven Design)的 Bounded Context 模式允許不同情境中存在同名型別:
- 大型領域無法用單一統一模型完整表達。
- 切分為「有界情境」後,跨情境出現同名型別是可接受的。
語言對「避免重複」的支援不足#
Java 泛型不支援原始型別。
java.util.Arrays中的binarySearch、sort等方法因此重複了七次——這是語言限制造成的 Duplicate Abstraction,不是設計者的疏忽。