介面隔離原則 (ISP)

★★★★Studied

FromAgile Principles, Patterns, and PracticesFunctional Design

📚 From the Books

介面隔離原則 (ISP)ConceptPrinciples

客戶端不應被迫依賴它們不使用的方法。「肥胖介面(fat interface)」會在本來不相關的客戶端之間製造有害耦合——某個客戶端不需要的方法一改,其他客戶端也被迫重編、重部署。解法:把肥胖介面拆成客戶端專屬的小介面。

🧠 When to Use

TIP

訊號:一個介面塞滿了「這群客戶端要、那群客戶端根本不碰」的方法;改了 A 客戶端才用的方法,卻害 B、C 客戶端全部重新編譯——這是介面污染的味道。

⚖️ Structure & Variants

案例 — ATM 的肥胖 UI 介面
// ❌ 一個大 UI 介面涵蓋所有交易的畫面需求
interface Ui {
    fun requestDepositAmount()
    fun requestWithdrawalAmount()
    fun requestTransferAmount()
    fun informInsufficientFunds()
}
// DepositTransaction 只用 requestDepositAmount,卻依賴整個 Ui
// → 改了 withdrawal 相關方法,Deposit / Transfer 也被迫重編

// ✅ 拆成客戶端專屬介面,各交易只依賴自己要的
interface DepositUi    { fun requestDepositAmount() }
interface WithdrawalUi { fun requestWithdrawalAmount(); fun informInsufficientFunds() }
interface TransferUi   { fun requestTransferAmount(); fun informInsufficientFunds() }
// 實作類別可同時實作三者;但各交易的參數型別只收自己那個介面
  • 拆完每個交易只看到、只依賴它真正需要的方法,胖介面在不相關客戶端間製造的連鎖反應就被打斷。
用 Adapter 委派分離 — Door / Timer
// Door 不該為了少數 TimedDoor 而被迫實作 TimerClient(介面污染)
interface TimerClient { fun timeOut(id: Int) }

// 用一個 adapter 把「計時回呼」從 Door 介面隔開
class DoorTimerAdapter(private val door: TimedDoor) : TimerClient {
    override fun timeOut(id: Int) = door.doorTimeOut(id)
}
  • 這也是為什麼 ISP 常和 Adapter 搭配:Door 介面保持乾淨,計時需求走 adapter 委派。另一種更簡潔的做法是讓 TimedDoor 直接同時實作 DoorTimerClient 兩個獨立介面。
Polyadic 勝過 Monadic
  • 把隔離後的介面又塞回一個全域物件(UIGlobals.ui),等於前功盡棄——每個交易仍間接依賴整包。
  • Monadic:所有客戶端共用同一個介面參數;Polyadic:每個客戶端各自接收它需要的介面。後者才是真正的隔離。

⚠️ Misuse & Anti-patterns

🔑 Takeaways

✍️ My Notes

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

📖 Further Reading