單一職責原則 (SRP)

★★★★★Studied

FromAgile Principles, Patterns, and PracticesFunctional Design

📚 From the Books

單一職責原則 (SRP)ConceptPrinciples

一個類別應該只有一個被變更的理由。職責=變更的軸線;如果你能想出一個以上的理由去修改一個類別,它就承擔了一個以上的職責。SRP 是 SOLID 五原則的第一個,本質是把「內聚性」綁到「誰會要求你改」上。

🧠 When to Use

TIP

訊號:改 A 功能卻莫名弄壞 B 功能;一個類別同時混了「商業規則」「畫面呈現」「持久化」;或某使用者只想用其中一小塊,卻被迫連帶依賴一整包不相關的東西。

⚖️ Structure & Variants

經典案例 — Rectangle 的雙重職責
// ❌ 同一個類別扛了兩個來自不同情境的職責
class Rectangle(val width: Double, val height: Double) {
    fun area(): Double = width * height   // 幾何計算 App 要用
    fun draw() { /* 呼叫 GUI 繪圖 */ }     // 圖形介面 App 要用
}
// GUI 需求一改 → Rectangle 改 → 純計算的 App 也被迫重編、重測、重部署

// ✅ 沿變更軸線切開:幾何歸幾何,繪圖歸繪圖
class Rectangle(val width: Double, val height: Double) {
    fun area(): Double = width * height
}
class RectangleView(private val rect: Rectangle) {
    fun draw() { /* 繪圖只依賴幾何資料,不反過來污染它 */ }
}
  • 兩個職責來自不同的使用情境,就有兩條獨立的變更軸線,該分開。
介面層先分離 — Modem 的『圍籬後的妥協』
// 一個 Modem 混了「連線管理」和「資料通訊」兩個職責
interface DataChannel { fun send(c: Char); fun recv(): Char }   // 資料通訊
interface Connection  { fun dial(number: String); fun hangup() } // 連線管理

// 實作可以暫時仍在同一個類別,但客戶端只依賴自己需要的那個介面
class ModemImpl : DataChannel, Connection {
    override fun send(c: Char) { /* ... */ }
    override fun recv(): Char = TODO()
    override fun dial(number: String) { /* ... */ }
    override fun hangup() { /* ... */ }
}
  • 即使實作內部還沒完全解耦,介面分離已足以保護客戶端不感知這種耦合——書中稱之為「圍籬後的妥協(a kludge behind a fence)」。

⚠️ Misuse & Anti-patterns

🔑 Takeaways

✍️ My Notes

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

📖 Further Reading

🔗 Dive Deeper