不要把變數宣告為具體 class 的實例,只依賴抽象所定義的 interface。 這是 GoF 全書 Design Pattern 的共通主題,也是它三大核心策略之首(另兩者:偏好組合勝於繼承、找出變動點並封裝)。因為 polymorphism 讓擁有相同 interface 的物件能在 run-time 互相替換,你的程式便不再被綁死在某個實作上。它跟 DIP 是同一件事的兩面:DIP 說「依賴方向朝抽象」,這條說「宣告型別用抽象」。
🧠 When to Use
TIP
訊號:你的變數、參數、回傳型別滿是具體 class 名(ArrayList、SqlProductRepository),想換實作就得改一整片呼叫端;或你正在寫一個希望被別人擴充的 framework。
- 想讓實作可在 run-time 替換(測試替身、不同資料來源、不同演算法)。
- 設計 framework / toolkit:讓使用者靠實作 interface 來擴展,而 framework 反過來呼叫他們(控制反轉)。
- 一群物件應被一致地對待,只要它們回應相同的請求集合。
⚖️ Structure & Variants
Interface、Type 與 Polymorphism
- Interface=物件所有 operation signature 的集合,定義了它能接收哪些請求。
- Type=特定 interface 的名稱;一個物件可擁有多個 type,不同物件也可共享同一 type。
- Dynamic binding 讓請求在 run-time 才綁到具體實作;Polymorphism 讓相同 interface 的物件能互換。
// ❌ 綁死具體實作:換 repository 就得改呼叫端
val repo: SqlProductRepository = SqlProductRepository(conn)
// ✅ 宣告成抽象,具體型別只在建立處出現一次
val repo: ProductRepository = SqlProductRepository(conn)
// repo 之後可換成 InMemoryProductRepository、CachingProductRepository… 呼叫端不動- 兩個好處:呼叫端不必知道用的是哪個實作,也不必知道實作所屬的 class——只要它符合 interface。
Class inheritance vs Interface inheritance
GoF 特別區分兩種繼承,很多語言(如 C++)混為一談,但實務上分野關鍵:
- Class inheritance(實作繼承):定義物件的實作,是程式碼與表示的共享機制。
- Interface inheritance / subtyping:定義物件何時可替代另一物件。
「對介面寫程式」依賴的是後者——許多 pattern(如 Chain of Responsibility)要求物件共享 type 卻不必共享實作。
NOTE
這也連到 GoF 另一條原則:Favor object composition over class inheritance。組合(黑箱重用)透過 interface 互動、不破壞封裝、可在 run-time 替換;繼承(白箱重用)是 compile-time 綁定,父類變動會連累子類。詳見 組合優於繼承。 :::
抽象類別 vs 介面:不是誰比較好,而是各有側重
《Design Patterns Explained》從設計層點出差異:
- 抽象類別:把相關實作收攏(focus on encapsulating implementations),可帶共同狀態與預設行為,仍可遵循依賴倒置。
- 介面:思考「使用方需要什麼樣的介面」,可拆得更細、更精簡(thin interface),趨向 ISP。
實務組合拳:先用介面定義最小契約,再用抽象類別實作以共享預設行為。
它是 GoF 模式的共通骨幹
「對介面寫程式」不是孤立原則,而是散落在多數模式裡的底層假設:
- Strategy / State:把演算法、狀態表示為實作共同 interface 的物件,run-time 抽換。
- Bridge / Abstract Factory:抽象與實作各自沿 interface 變動,互不牽連。
- Decorator / Proxy / Adapter:包一層、共享(或轉換)interface。
Framework 強調 design reuse 而非只是 code reuse,並帶來控制反轉——成熟的 framework 通常整合多個 pattern,全都建立在「消費者只依賴 interface」之上。
⚠️ Misuse & Anti-patterns
- 為不變的細節硬抽介面:像
String、Int這種幾乎不會變的具體型別,依賴它並無大礙——原則瞄準的是易變的依賴,別把它當教條(同 DIP 的經驗法則例外)。 - 把介面當型別別名:只是把
SqlProductRepository改叫IProductRepository、一對一貼著實作——沒有從「使用方需要什麼」反推,介面就沒帶來替換彈性。 - 過度增設間接層:每個 pattern 都靠多一層間接換彈性,這會讓設計更複雜、犧牲效能;只有在確實需要該彈性時才抽象。
- 誤把它等同於「一律用 interface 關鍵字」:重點是依賴抽象、可替換的型別,抽象類別同樣算數;語言關鍵字不是判準。
🔑 Takeaways
- 核心一句:宣告型別用抽象,不用具體 class——polymorphism 讓相同 interface 的物件 run-time 互換,程式不被實作綁死。
- 它是 GoF 全書共通主題,也是三大策略之首;與 DIP 互為表裡、與 組合優於繼承 相互支撐。
- 關鍵區分:interface inheritance(可替換)≠ class inheritance(共享實作);「對介面寫程式」靠的是前者。
- 抽象類別 vs 介面沒有絕對優劣——先用介面定最小契約,再用抽象類別共享預設行為;且只對易變依賴抽象,別為穩定型別徒增層次。
No notes yet — jot your takeaways or Q&A here.