🎯 什麼情境該想到我

當你「同一件事有好幾種可互換的做法」的時候——排序規則、計費方案、壓縮/加密演算法、驗證策略、重試退避。
★ 在 Go 和 TS 裡,它常常退化成「一個函式型別」——這是本頁最重要的觀察。

⚙️ 怎麼用

意圖

定義一系列演算法,把每一個都封裝起來,並使它們可以互相替換。策略模式讓演算法可以獨立於使用它的客戶端而變化。

它要消滅的是這個:

// ❌ 每加一種計費方式就要改這個函式
func Price(kind string, base float64) float64 {
    switch kind {
    case "normal":  return base
    case "vip":     return base * 0.8
    case "student": return base * 0.5
    ...             // 無限成長
    }
}

★ Go 的長相:多數情況下「一個 func 型別」就夠了

// ★ 這就是 Strategy
type PricingStrategy func(base float64) float64
 
var (
    Normal  PricingStrategy = func(b float64) float64 { return b }
    VIP     PricingStrategy = func(b float64) float64 { return b * 0.8 }
    Student PricingStrategy = func(b float64) float64 { return b * 0.5 }
)
 
type Checkout struct{ pricing PricingStrategy }      // Context
 
func (c Checkout) Total(base float64) float64 {
    return c.pricing(base)
}

★ GoF 需要 Strategy 介面 + 個 ConcreteStrategy 類別,是因為 C++/Java 沒有頭等函式。
Go 有,所以整個類別階層消失了。

什麼時候仍然該用介面而非函式:

用函式用介面
策略只有一個行為★ 策略有多個相關方法(Encode/Decode、Compress/Decompress)
無狀態★ 需要狀態(連線、快取、計數器)
不需要名字★ 需要 Name()、String() 之類的元資訊
// 需要多方法時:
type Codec interface {
    Encode(io.Writer, any) error
    Decode(io.Reader, any) error
    ContentType() string
}

標準庫裡的 Strategy

案例策略是什麼
slices.SortFunc(s, cmp)★ cmp func(a, b E) int 就是排序策略
http.Client{Transport: ...}RoundTripper 是傳輸策略
slog.New(handler)Handler 是輸出策略(也是 橋接模式)
tls.Config.GetCertificate憑證選擇策略

TypeScript 的長相

type PricingStrategy = (base: number) => number;
 
const strategies = {
  normal:  (b: number) => b,
  vip:     (b: number) => b * 0.8,
  student: (b: number) => b * 0.5,
} satisfies Record<string, PricingStrategy>;   // ★ satisfies 保留字面量型別又檢查形狀
 
type Tier = keyof typeof strategies;           // "normal" | "vip" | "student"
 
const total = (tier: Tier, base: number) => strategies[tier](base);

★ satisfies + keyof typeof 是 TS 的關鍵組合(見 工具-satisfies與型別標註):
它讓 Tier 自動從實作推導出來——加一個策略,型別自動跟著更新,不用維護兩份清單。
這是 Go 做不到的(Go 得手動維護 enum 與 map)。

★ Strategy 與三個孿生模式的區分

差別
狀態模式★ 結構完全相同。策略是「外部主動挑一種」;狀態是「內部隨狀態自動切換,且狀態自己會轉換」
橋接模式Bridge 的抽象那側有自己的階層,且通常建構時決定;Strategy 常在執行期換
範本方法模式範本方法換的是步驟(繼承);策略換的是整個演算法(組合)

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • ★ 只有兩三種且幾乎不變 → 直接 switch 就好。
    引入策略要多一個型別、多一層間接、多一個「誰決定用哪個」的問題。
    判準:這些分支會不會被獨立地加/改?會 → 策略;不會 → switch。
  • ★ 選擇的責任被推給呼叫端。呼叫端得知道有哪些策略、差在哪。
    → 常需要再配一個 工廠方法模式 把「字串 → 策略」的對應收攏起來。
  • 策略之間必須真的可互換。簽章一樣但語義不同(一個同步一個非同步、一個會 panic 一個不會)
    就不是策略,只是長得像。
  • 函式型策略不好除錯:堆疊裡是匿名函式。給它們具名(var VIP PricingStrategy = vipPricing)。
  • Go 的函式型策略無法比較(func 不可比較),也無法當 map key。需要辨識就用介面 + Name()。
  • 不要把策略當成「所有 if 的解藥」。有些條件分支就是業務邏輯,拆成策略只會讓流程散落各處、更難讀。

🔗 相關工具