🎯 什麼情境該想到我
當你「打開一個類別,發現它大部分的方法都只是把呼叫原封不動轉交給另一個物件」的時候——它自己幾乎不做事,只當轉手。
⚙️ 怎麼用(步驟 / 公式)
這味道是什麼徵兆:封裝與委託是好東西,但過了頭就變成問題——當一個類別半數以上的方法都只是委派給別人,這層中間人就只剩間接的成本、沒帶來價值。
通常用哪些重構手法對治:
- 移除中間人(Remove Middle Man):讓呼叫端直接與真正做事的物件對話,拿掉多餘轉手。
- 以繼承取代委託 / 以委託取代子類(Replace Superclass with Delegate / Replace Subclass with Delegate):視關係本質,在繼承與委託之間調整結構。
- 若只有少數幾個委派方法麻煩,可內聯函式把它們併掉。
★ 定義與怎麼具體認出來
// ★ 訊號:一個型別的方法,一半以上只是「轉發給同一個委託物件」
type Person struct{ department *Department }
func (p *Person) ChargeCode() string { return p.department.ChargeCode }
func (p *Person) Manager() *Person { return p.department.Manager }
func (p *Person) Budget() int { return p.department.Budget }
func (p *Person) HeadCount() int { return p.department.HeadCount }
func (p *Person) Location() string { return p.department.Location }
// ★ 五個方法、零行邏輯 —— Person 只是 Department 的傳聲筒可量化的判準:
# ★ 數「一行 return x.field.Something 的方法」佔比
grep -cE '^\s*return [a-z]+\.[a-z]+\.[A-Za-z]+(\(.*\))?$' --include='*.go' -r .★ 經驗門檻:轉發方法佔一個型別方法總數的一半以上,就是這個味道。
★★ 它的來源:隱藏委託關係 做過頭
[[訊息鏈]](暴露太多) ──隱藏委託關係──▶ ★ 剛好 ──再包更多──▶ 中間人(隱藏太多)
★ 這兩個味道是同一條軸的兩端,而「剛好」的位置會隨需求移動。
所以不要追求「一次調對」——它會來回擺動,那是正常的。
判準:轉發方法的數量。
一、兩個 → 你在保護一個實作細節,這是對的。
十幾個 → 你其實只是想暴露那個委託物件,那就大方暴露(反向操作)。
★★ Go 特有:這五種「中間人」是設計,不是味道
// ① ★ 裝飾者 / middleware —— 轉發就是它的工作
type loggingStore struct {
Store // ★ 嵌入介面:其餘方法自動轉發
log *slog.Logger
}
func (s loggingStore) Put(ctx context.Context, k string, v []byte) error {
s.log.Info("put", "key", k)
return s.Store.Put(ctx, k, v) // ★ 只覆寫這一個,其餘全轉發 —— 這是 [[裝飾者模式]]
}
// ② ★ 配接器 —— 存在的意義就是轉換與轉發([[配接器模式]])
// ③ ★ 外觀 —— 為複雜子系統提供簡化入口([[外觀模式]])
// ④ ★ 代理 —— 快取、延遲載入、權限檢查([[代理模式]])
// ⑤ ★★ 防腐層 —— 刻意把外部模型隔在邊界外([[防腐層]])★★ 這五種的轉發都是「有理由的」:它們在轉發的同時做了一件事
(記 log、轉換型別、簡化介面、加快取、隔離外部模型)。
純粹的中間人是「什麼都沒做」的轉發。
★ Go 的嵌入讓這件事更微妙:
type Service struct{ *Repo } // ★ 一行就轉發了 Repo 的所有匯出方法這不是中間人味道(沒有樣板程式碼要維護),但它有另一個問題:
Repo 新增一個匯出方法 → Service 的公開 API 自動變大,你沒得選。
→ 見 工具-Go嵌入機制。
★ 對治手法
| 情況 | 對治 |
|---|---|
| 一半以上的方法是純轉發 | ★ 公開委託物件(p.Department()),刪掉轉發方法 |
| 整個型別只是轉發 | 內聯類別——把它合併掉 |
| 轉發的同時應該做事 | 搬移函式——把邏輯搬進來,它就不再是中間人 |
| 只有幾個轉發但很囉唆 | Go 的嵌入(⚠️ 代價是 API 全部攤出來) |
⚠️ 移除中間人的代價
公開委託物件 = 把它變成公開契約。 之後想換掉 Department 的型別,
就是破壞性變更了——而原本有轉發層時,你可以自由替換。
★ 所以「有幾個轉發方法」不是唯一判準,還要問「這個委託物件穩定嗎」:
不穩定(外部套件、會換的實作)→ ★ 保留轉發層是對的,即使方法很多(那就是 防腐層)。
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
委託本身不是壞事——為了隱藏委託(避免訊息鏈)而保留少量轉發是合理的。
-
中間人與訊息鏈是一體兩面:修訊息鏈可能製造中間人,反之亦然,要在兩者間取平衡點。
-
★★ 裝飾者、配接器、外觀、代理、防腐層都是「刻意的中間人」,不是味道。它們在轉發的同時做了一件事。
-
★ 委託物件不穩定時(外部套件、會替換的實作),保留大量轉發是對的——那是防腐層。
-
★ 公開委託物件 = 把它變成公開契約,之後換不掉。移除中間人不是免費的。
-
Go 的嵌入一行就轉發全部方法,沒有樣板成本——但代價是被嵌入型別新增方法時,你的 API 自動變大。
-
這個味道和 訊息鏈 是同一條軸的兩端,「剛好」的位置會隨需求移動。不要追求一次調對。
-
判準不只是數量:五個「什麼都沒做」的轉發是味道;二十個「隔離外部模型」的轉發是設計。