開放封閉原則 (OCP)

★★★★★Studied

FromAgile Principles, Patterns, and PracticesFunctional Design

📚 From the Books

開放封閉原則 (OCP)ConceptPrinciples

軟體實體應該對擴展開放、對修改封閉:加新行為時,不必去改動既有源碼。由 Bertrand Meyer 提出,靠抽象化解這看似矛盾的兩個目標——模組依賴一個固定的抽象(對修改封閉),新行為以新的實作類別加入(對擴展開放)。

🧠 When to Use

TIP

訊號:每次「加一種類型」都要回去改同一個 when(type) / if...else if 鏈;一個小需求牽動一片模組;本該只是「加東西」的工作變成「到處改」。

⚖️ Structure & Variants

違反 vs 遵循 — DrawAllShapes
// ❌ 每加一種圖形就要改這個函式 —— 對修改不封閉
fun drawAll(shapes: List<Any>) {
    for (s in shapes) when (s) {
        is Square -> drawSquare(s)
        is Circle -> drawCircle(s)
        // 加 Triangle?回來再補一條 else if...
    }
}

// ✅ 依賴抽象,新增圖形完全不動 drawAll
interface Shape { fun draw() }
class Square : Shape { override fun draw() { /* ... */ } }
class Circle : Shape { override fun draw() { /* ... */ } }

fun drawAll(shapes: List<Shape>) = shapes.forEach { it.draw() }
// 新增 Triangle:只寫一個新 class 實作 Shape,drawAll 一行都不改
  • 這正是 StrategyTemplate Method 兩個模式達成 OCP 的方式:把可變步驟宣告成抽象,客戶端同時 open 又 closed。
沒有『對所有變更都封閉』這回事 — 資料驅動封閉
  • 對「新增圖形種類」封閉的設計,遇到「圓形要畫在正方形之前」這種排序需求,就又違反 OCP 了。沒有任何結構對所有變更方向都天然。
  • 解法之一是把易變的規則抽到外部資料表(Data-Driven):
// 繪製順序抽成一張優先權表,新增種類只在表裡加一行
val priority = mapOf(Circle::class to 1, Square::class to 2)
val ordered = shapes.sortedBy { priority[it::class] ?: Int.MAX_VALUE }
  • 各 Shape 與 drawAll 都對「順序變更」保持封閉;變化被關進資料裡。

⚠️ Misuse & Anti-patterns

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading