🎯 什麼情境該想到我

當你「想控制別人怎麼存取某個東西」的時候——延遲載入、快取、權限檢查、遠端呼叫、計費計量。
★ 和 裝飾者模式 結構幾乎一樣,差別在「意圖」——這是面試最常追問的一點。

⚙️ 怎麼用

意圖與四種類型

為另一個物件提供一個替身或佔位符,以控制對它的存取。

類型做什麼實務例子
★ 遠端代理讓遠端物件用起來像本地的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。

🔗 相關工具