🎯 什麼情境該想到我
當你「完成一件事要按順序呼叫五個物件的十個方法」的時候,或「想把一整群第三方相依收攏在一處,之後好換」的時候。
★ 它是二十三個模式裡最簡單、也最常被無意識使用的一個——你寫過的每一個 service 層幾乎都是 Facade。
⚙️ 怎麼用
意圖
為子系統中的一組介面提供一個統一的高層介面,讓子系統更容易使用。
Client ──▶ Facade ──┬──▶ SubsystemA
├──▶ SubsystemB
└──▶ SubsystemC
★ 客戶端只認識 Facade
★ 但 Facade 不阻止你在需要時直接用子系統(這點很重要)
★ Go 的長相
// 沒有 Facade:呼叫端要知道整個流程
func handleOrder(...) error {
if err := inventory.Reserve(ctx, items); err != nil { return err }
charge, err := payments.Charge(ctx, card, total)
if err != nil {
inventory.Release(ctx, items) // ★ 補償
return err
}
o, err := orders.Create(ctx, items, charge.ID)
if err != nil {
payments.Refund(ctx, charge.ID) // ★ 補償
inventory.Release(ctx, items)
return err
}
notifications.SendConfirmation(ctx, o)
analytics.Track(ctx, "order_placed", o)
return nil
}
// ★ 有 Facade:呼叫端只看得到意圖
type OrderService struct {
inventory Inventory
payments Payments
orders OrderRepo
notifier Notifier
analytics Analytics
}
func (s *OrderService) PlaceOrder(ctx context.Context, req PlaceOrderRequest) (*Order, error) {
// 上面那整段流程與補償邏輯收在這裡
}
// 呼叫端
order, err := svc.PlaceOrder(ctx, req)★ Go 的 Facade 通常不叫 Facade,叫
Service、Client、Manager。
你寫的每個應用服務層基本上都是這個模式——認出它的價值在於知道「該把什麼放進去、什麼不該」。
標準庫裡的 Facade
| 案例 | 收攏了什麼 |
|---|---|
http.Get(url) | ★ 背後是 DefaultClient → Transport → 連線池 → TLS → 重導向處理 |
os.ReadFile(name) | Open + Read + Close + 錯誤處理 |
sql.DB | 連線池、prepare 快取、重試 |
slog.Info(...) | Default handler → 格式化 → 輸出 |
★ Go 標準庫的「便利函式」幾乎都是 Facade,而且它們保留了下層 API——
http.Get 不夠用時你可以自己組 http.Client{Transport: ...}。這正是好 Facade 的特徵。
TypeScript 的長相
// 一個模組本身就是很自然的 Facade
// order-service.ts
export async function placeOrder(req: PlaceOrderRequest): Promise<Order> {
// 收攏 inventory / payments / orders / notifier
}★ ES module 的「只匯出你要的東西」本身就是 Facade 的邊界。
不 export 的內部函式就是被隱藏的子系統。
★ 它真正的三個價值
| 價值 | 說明 |
|---|---|
| ① 降低認知負荷 | 呼叫端不用知道五個子系統的順序與補償 |
| ② ★ 收攏變更 | 第三方 SDK 升級/換供應商時,只改 Facade 一處(見 防腐層) |
| ③ 定義事務邊界 | 「一次 PlaceOrder」是一個有意義的單位,可以在這裡做事務、重試、觀測 |
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
- ★ 最大的風險:Facade 變成上帝物件(God Object)。
OrderService一開始有 3 個方法,兩年後有 47 個、相依 12 個子系統——
它從「簡化入口」變成「什麼都往裡塞的垃圾場」。
→ 判準:Facade 的方法應該對應「使用者的一個完整意圖」。 超過 7–10 個方法就該考慮拆分。 - ★ 不要封死下層。好的 Facade 是捷徑不是牢籠——
90% 的情境用 Facade,剩下 10% 要能直接用子系統。
封死下層會逼使用者繞過你或 fork 你。(http.Getvs 自組http.Client就是正解。) - 不要讓 Facade 變成純轉發。每個方法都只是
return s.sub.Method(...)的話,它沒有提供任何價值,只是多一層。 - ★ 和 配接器模式 的界線:
Adapter 是一對一的介面轉換(因為不相容);Facade 是多對一的簡化(因為太複雜)。
Adapter 解決「合不合」,Facade 解決「好不好用」。 - 和 防腐層 的關係:防腐層是 DDD 的概念,它通常「是」一個 Facade,但多了一層語義翻譯
(把外部系統的模型翻譯成你的領域模型)。只是簡化不翻譯的話,它只是 Facade 不是 ACL。 - 測試:Facade 讓單元測試變好寫(只要 mock 一個),但也可能掩蓋整合問題——
子系統之間的互動仍然需要整合測試。