🎯 什麼情境該想到我
當你「一群物件彼此互相呼叫,連線數已經爆炸成 」的時候——
表單元件互相影響(選了 A 就要禁用 B)、聊天室、多人協作、UI 元件協調。
⚙️ 怎麼用
意圖
用一個中介物件來封裝一系列物件的互動。中介者使各物件不需要顯式地互相引用,從而使其耦合鬆散。
❌ 沒有中介者:n 個物件兩兩相連 → O(n²) 條關係
A ⇄ B ⇄ C
⇅ ╳ ⇅ 每加一個元件,要改所有既有元件
D ⇄ E ⇄ F
✅ 有中介者:n 條
A B C
╲ │ ╱
Mediator ★ 每個元件只認識中介者
╱ │ ╲
D E F
★ 和 Observer 的差別(必考)
| 中介者 | 觀察者模式 | |
|---|---|---|
| 中心知不知道成員 | ★ 知道(它要協調它們) | ❌ 不知道(Subject 不認識訂閱者) |
| 方向 | ★ 多對多(同儕之間) | 一對多(Subject → Observers) |
| 中心的角色 | ★ 有邏輯(決定誰該做什麼) | 只是轉發(沒有業務邏輯) |
| 成員之間 | 互不認識,透過中介者 | Observer 也不認識彼此 |
★ 一句話:觀察者的中心是「啞的廣播器」,中介者的中心是「聰明的協調者」。
如果你的「事件匯流排」開始包含「收到 X 事件時要去做 Y」的邏輯,它已經是中介者了。
★ Go 的長相
// 元件只認識中介者
type Component interface {
SetMediator(Mediator)
}
type Mediator interface {
Notify(sender Component, event string)
}
// ★ 協調邏輯全部集中在這裡
type dialog struct {
checkbox *Checkbox
textbox *TextBox
submit *Button
}
func (d *dialog) Notify(sender Component, event string) {
switch {
case sender == d.checkbox && event == "toggled":
d.textbox.SetEnabled(d.checkbox.Checked()) // ★ 元件之間的規則在這
d.revalidate()
case sender == d.textbox && event == "changed":
d.revalidate()
}
}
func (d *dialog) revalidate() {
d.submit.SetEnabled(!d.checkbox.Checked() || d.textbox.Text() != "")
}★ 價值在於:Checkbox 完全不知道 TextBox 和 Button 存在。
規則「勾選時才啟用文字框,且文字框非空才能送出」在一個地方讀得到。
★ Go 更慣用的形態:一個協調函式 / channel hub
// ★ 聊天室 hub —— Go 的經典中介者
type Hub struct {
clients map[*Client]bool
broadcast chan Message
register chan *Client
unregister chan *Client
}
func (h *Hub) Run() {
for {
select { // ★ 單一 goroutine 序列化所有狀態變更
case c := <-h.register:
h.clients[c] = true
case c := <-h.unregister:
delete(h.clients, c)
close(c.send)
case m := <-h.broadcast:
for c := range h.clients {
select {
case c.send <- m:
default: // ★ 慢的 client 直接踢掉
delete(h.clients, c)
close(c.send)
}
}
}
}
}★ 這是 Go 版 WebSocket 聊天室的標準骨架(
gorilla/websocket的官方範例就長這樣)。
用「單一 goroutine + select」序列化所有狀態變更,完全不需要 mutex
——這正是 工具-靠通訊共享記憶體 的精神。
TypeScript 的長相
// 在 TS 裡,中介者常常就是一個 store / reducer
type Action =
| { type: "toggleAdvanced" }
| { type: "setName"; value: string };
function reducer(state: FormState, action: Action): FormState {
switch (action.type) {
case "toggleAdvanced": {
const advanced = !state.advanced;
// ★ 元件之間的規則集中在這
return { ...state, advanced, canSubmit: !advanced || state.name !== "" };
}
case "setName":
return { ...state, name: action.value, canSubmit: !state.advanced || action.value !== "" };
}
}★ Redux 的 store 就是一個中介者——元件只 dispatch,不互相呼叫。
這也解釋了為什麼前端狀態管理會演化成這個形狀:它解的正是 的元件耦合問題。
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
- ★ 最大的風險:中介者變成上帝物件。
所有互動邏輯都集中過來 → 它變成一個幾千行、什麼都知道的怪物。
你把 的複雜度從「元件之間」搬到了「中介者內部」,沒有消滅它。
→ 對策:- 按領域拆成多個中介者(表單一個、導航一個),不要一個管全部
- 中介者只放協調邏輯,業務規則下放到領域物件
- 超過幾百行就該拆
- ★ 元件數少(2–3 個)不需要中介者。直接互相引用更簡單清楚。
它的價值在 夠大、且關係夠密集的時候。 - 除錯:所有互動都經過中介者,堆疊裡看不到「誰觸發了誰」。要 log。
- 中介者本身成為單點:它掛了所有協調都停擺;它變慢所有互動都變慢。
- 不要把中介者當成事件匯流排用。中介者知道它的成員;事件匯流排不知道。
兩者混用會得到「既不知道誰在聽、又塞滿業務邏輯」的最糟組合。 - 並行下的中介者要序列化(Go 用單一 goroutine + channel,見上;或用 mutex)。