🎯 什麼情境該想到我
當你「想控制別人怎麼存取某個東西」的時候——延遲載入、快取、權限檢查、遠端呼叫、計費計量。
★ 和 裝飾者模式 結構幾乎一樣,差別在「意圖」——這是面試最常追問的一點。
⚙️ 怎麼用
意圖與四種類型
為另一個物件提供一個替身或佔位符,以控制對它的存取。
| 類型 | 做什麼 | 實務例子 |
|---|---|---|
| ★ 遠端代理 | 讓遠端物件用起來像本地的 | gRPC / RPC 的 client stub |
| 虛擬代理 | 延遲建立昂貴的物件 | ORM 的 lazy loading、大圖的縮圖佔位 |
| 保護代理 | 存取前檢查權限 | 授權檢查層 |
| 智慧參考 | 存取時附帶做別的事 | 引用計數、快取、計量、連線池 |
Client ──▶ Subject(介面)
▲
├── RealSubject(真正的實作)
└── Proxy ──持有/建立──▶ RealSubject
★ Proxy vs Decorator(必考的區分)
| Proxy | 裝飾者模式 | |
|---|---|---|
| 意圖 | 控制存取 | 增加功能 |
| 對使用者 | 透明(不知道有代理) | 明確(使用者主動選要包哪些) |
| 層數 | 通常一層 | 可疊加多層 |
| 誰決定 | 設計/框架決定 | 呼叫端決定 |
| 是否持有實體 | 可能自己建立(虛擬代理) | 一定由外部傳入 |
★ 一句話:
「使用者知不知道它存在?」 —— 知道且主動疊加 → Decorator;不知道、以為在用真的 → Proxy。實務上這條線很模糊(一個「加了快取的 repository」算哪個?)。
不要為了分類糾結——重點是你有沒有把「控制存取」和「增加功能」這兩個意圖想清楚。
★ Go 的長相
① 虛擬代理(延遲載入)
type Config interface{ Get(key string) string }
type lazyConfig struct {
once sync.Once
real Config
load func() Config
}
func (c *lazyConfig) Get(key string) string {
c.once.Do(func() { c.real = c.load() }) // ★ 第一次存取才真的載入
return c.real.Get(key)
}② 保護代理
type authProxy struct {
next Repository
user *User
}
func (p authProxy) Delete(ctx context.Context, id string) error {
if !p.user.Can("delete", id) {
return ErrForbidden // ★ 連真的物件都不碰
}
return p.next.Delete(ctx, id)
}③ 快取代理
type cachedRepo struct {
next Repository
cache *lru.Cache[string, *Item]
}
func (r *cachedRepo) Get(ctx context.Context, id string) (*Item, error) {
if v, ok := r.cache.Get(id); ok { return v, nil }
v, err := r.next.Get(ctx, id)
if err == nil { r.cache.Add(id, v) }
return v, err
}標準庫與生態
| 案例 | 類型 |
|---|---|
httputil.ReverseProxy | ★ 遠端代理,名字直接叫 Proxy |
| gRPC 生成的 client stub | 遠端代理——呼叫 client.GetUser(ctx, req) 就像本地方法 |
sql.DB | ★ 智慧參考:它不是一條連線,是一個連線池的代理 |
http.Client 的 Transport | 可插拔的代理層 |
Node 的 Proxy 物件 | ★ 語言內建的代理(攔截屬性存取),ORM 與響應式框架用它做 lazy/追蹤 |
TypeScript 的長相
// ★ JS/TS 有語言層的 Proxy —— 這是 Go 沒有的能力
const lazy = <T extends object>(factory: () => T): T =>
new Proxy({} as T, {
get(target, prop, receiver) {
// 第一次存取任何屬性時才真正建立
const real = (factory as any)._v ??= factory();
return Reflect.get(real, prop, receiver);
},
});★
Proxy是 ORM(Prisma、TypeORM)與響應式框架(Vue 3)的核心機制——
它們攔截屬性存取來做延遲載入與相依追蹤。
⚠️ 但它有效能成本,而且讓除錯變困難(看到的不是真正的物件)。一般業務程式碼不要用。
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
- ★ 遠端代理最大的謊言是「讓遠端看起來像本地」。
它隱藏了:網路延遲、部分失敗、超時、重試、序列化成本。
→ 這正是 工具-分散式事務的取捨 與「分散式運算的謬誤」要對抗的東西。
介面上要保留ctx context.Context與error,不要讓它「太像」本地呼叫。 - ★ 快取代理的失效是最難的部分。加快取容易,知道什麼時候該讓它失效才是問題。
沒想清楚失效策略就加快取,會產生「資料明明改了但使用者看到舊的」這類難以重現的 bug。 - 延遲載入會把錯誤延後到奇怪的地方。
config.Get()在深層業務邏輯裡才炸出「連不上設定中心」——
考慮在啟動時就 eager 載入關鍵相依,快速失敗(fail fast)。 - 代理層會讓 profiling 與追蹤失真。時間花在哪一層要能分辨(見 工具-遙測與監控)。
- 不要為了「以後可能要加快取」預先放一層 proxy。YAGNI。
- 保護代理不能取代真正的授權。它是一層方便的檢查,不是安全邊界——
真正的授權要在信任邊界上做,不能只靠一個可繞過的應用層 wrapper。