找出變動軸並把它隔離:概念上穩定的部分(commonality)收成抽象,底下會變的具體(variability)做成衍生。這是設計模式共同的底層動機——CVA 與設計模式最終得到極相似的結構,正因為兩者遵循同一組原則:找出變動、封裝、設計到介面、聚合勝於繼承。
🧠 When to Use
TIP
訊號:整合新程式進既有系統的成本,比新程式本身的開發成本還高——通常是元件之間耦合過緊。或你發現自己(越有經驗越容易)太早在想「實體之間如何關聯」,卻還沒確認「實體本身對不對」。
- 手上沒有現成模式可套,仍需要一套「怎麼找出對的抽象」的通用方法。
- 一個問題領域裡明顯有「一個穩定概念、底下多個會變的具體」的形狀。
- 想在寫關聯、寫實作之前,先把該領域的抽象骨架擺對。
⚖️ Structure & Variants
CVA 兩階段 — 先找概念,再從使用方反推介面
共通與差異分析(Commonality and Variability Analysis, CVA)是一套全域可用的找抽象方法,任何問題都適用。設計流程:
- 識別 commonality(概念)
- 從 commonality 建立抽象
- 用 variability(具體變化)建立衍生
- 觀察 commonality 之間的關係
找概念的一個技巧:兩兩比對「這是那個的變化嗎?」「這兩者是某概念的不同變化嗎?」找到概念後,每個 commonality 變成抽象類別、其變化變成衍生類別。
// commonality「Feature」→ 抽象;variability(slot/hole/cutout…)→ 衍生
interface Feature {
fun extractFrom(cadcam: CadCam)
}
class Slot : Feature { override fun extractFrom(cadcam: CadCam) { /* ... */ } }
class Hole : Feature { override fun extractFrom(cadcam: CadCam) { /* ... */ } }
// 加一種 feature 只是多一個衍生類別,抽象與使用方不動第二階段:從使用方視角反推每個抽象的介面(即依賴倒置原則的應用)。
一個 commonality 只承載一個議題
若把 CAD/CAM 問題拆成 V1Slots、V1Holes、V2Slots、V2Holes……,共通性看起來是「CAD/CAM features」,但其實混了兩個議題:CAD/CAM 版本 + feature 種類。一個 commonality 應只承載一個議題,否則設計凝聚會被破壞。
// ❌ 一個維度混了「版本」與「feature 種類」兩個會獨立變動的議題
class V1Slot; class V1Hole; class V2Slot; class V2Hole
// 每加一種 feature × 每多一個版本,類別數相乘爆炸
// ✅ 拆成兩條各自獨立的變動軸
interface Feature // 議題一:feature 種類
interface CadCam // 議題二:CAD/CAM 版本這條規則也是作者的個人紀律:從不讓一個類別同時承載兩個會獨立變動的議題,除非這兩者本就緊密耦合。對照 單一職責原則(一個類別只有一個被變更的理由)。
各模式都在封裝某一種變動
「封裝變動」是貫穿各模式的元原則,每個模式各自封裝一種變動軸:
- Bridge:封裝實作的變動——加新實作完全不用動既有類別(也是 OCP 的典型示範)。
- Abstract Factory:封裝「該建立哪一群物件」的變動。
- Adapter:把不同介面統一,配合其他模式的變動需求。
- Facade:通常不是封裝變動,但同一介面包不同子系統時可順便達成。
模式不只在封裝變動,也定義了物件之間的關係——例如 Bridge 同時定義抽象與實作這兩個變動軸如何關聯。變動找出來之後「用什麼實現」,見 組合優於繼承。
抽象類別 vs 介面 — 封裝什麼由此決定
- 抽象類別:把相關實作收攏(focus on encapsulating implementations),仍可遵循依賴倒置。
- 介面:思考「使用方需要什麼樣的介面」,可拆得更細、更精簡(thin interface)。
兩者不是一個比另一個好。可先用介面定義出最小契約,再用抽象類別實作以共享預設行為。
interface Feature { fun extractFrom(cadcam: CadCam) } // 使用方要的最小契約
abstract class AbstractFeature : Feature { // 收攏共同實作
protected fun log(msg: String) { /* 衍生類共享 */ }
}⚠️ Misuse & Anti-patterns
- 太早談關聯、太晚問對錯:設計時最常見的錯誤是先想「實體之間如何關聯」,卻沒先確認「實體本身對不對」——越有經驗越容易掉進去。
- 一個 commonality 混多個議題:把兩條會獨立變動的軸壓進同一個共通性,會破壞設計凝聚。
- 一個類別扛兩條獨立變動軸:除非兩者本就緊密耦合,否則不該讓一個類別同時承載兩個會獨立變動的議題。
- 把「可能的變動點」當成「現在就要寫」:模式提醒我們可能的變動點,但機會不等於必須立刻採用。透過模式建立的良好抽象,就足以容納未來的變動——即使那是當初沒預料到的變動。
🔑 Takeaways
- 封裝變動 = 找出變動軸並隔離;commonality → 抽象、variability → 衍生。
- CVA 找抽象、設計模式組關係,兩者互補;同遵一組原則:找出變動、封裝、設計到介面、聚合勝於繼承。
- CVA 全域可用,任何問題都適用;它與 Alexander 的脈絡設計、依賴倒置原則本質相同。
- 這是「識別並封裝變異」的方法;「對擴充開放對修改封閉」的目標見 OCP。
No notes yet — jot your takeaways or Q&A here.