客戶端不應被迫依賴它們不使用的方法。「肥胖介面(fat interface)」會在本來不相關的客戶端之間製造有害耦合——某個客戶端不需要的方法一改,其他客戶端也被迫重編、重部署。解法:把肥胖介面拆成客戶端專屬的小介面。
🧠 When to Use
TIP
訊號:一個介面塞滿了「這群客戶端要、那群客戶端根本不碰」的方法;改了 A 客戶端才用的方法,卻害 B、C 客戶端全部重新編譯——這是介面污染的味道。
- 一個介面因為要服務多種客戶端,塞入彼此不相關的方法。
- 某些實作類別被迫實作一堆它用不到的方法(常留空 → 也踩到 LSP)。
- 想切斷「不相關客戶端因共用胖介面而連動」的耦合鏈。
⚖️ 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直接同時實作Door與TimerClient兩個獨立介面。
Polyadic 勝過 Monadic
- 把隔離後的介面又塞回一個全域物件(
UIGlobals.ui),等於前功盡棄——每個交易仍間接依賴整包。 - Monadic:所有客戶端共用同一個介面參數;Polyadic:每個客戶端各自接收它需要的介面。後者才是真正的隔離。
⚠️ Misuse & Anti-patterns
- 胖介面 / 介面污染:一個介面服務多群客戶端、方法彼此不內聚——就是該拆的訊號。
- 偽隔離:介面拆了,卻用一個全域靜態物件重新聚合,客戶端仍依賴整體。要透過各自的參數注入需要的介面。
- 拆過頭:每個方法一個介面、彼此其實總是一起用——反而製造無意義的碎片。以「客戶群」為切割單位,不是以「方法數」。
🔑 Takeaways
- 目標不是「介面變小」,而是「客戶端不被迫依賴不需要的東西」。
- 按客戶群切介面;胖介面會在不相關客戶端之間傳染變更。
- 落地兩招:Adapter 委派,或讓一個實作同時實作多個獨立小介面;別用全域物件把隔離破功。
No notes yet — jot your takeaways or Q&A here.