意圖(Intent)#

定義一系列演算法,將每一個封裝起來,並使它們可以互換。Strategy 讓演算法的變化獨立於使用它的客戶端。

別名(Also Known As)#

  • Policy

動機(Motivation)#

將文字斷行拆分為多行的演算法有很多種。將所有演算法硬寫在需要它們的類別中會帶來問題:

  • 客戶端變得更複雜,尤其是需要支援多種演算法時
  • 不同情境下需要不同的演算法,不想全部都載入
  • 演算法作為客戶端的一部分時,難以新增或修改

解法是將不同的斷行演算法分別封裝成獨立的類別,稱之為 strategy

例如 Composition 類別負責維護文字排版的斷行資訊,但斷行策略由 Compositor 的子類別實作:

  • SimpleCompositor:逐行決定斷行位置
  • TeXCompositor:以段落為單位全域最佳化斷行
  • ArrayCompositor:每行固定數量的元素

Composition 持有一個 Compositor 的參照,需要重排時將責任委派給它。客戶端透過安裝不同的 Compositor 來選擇策略。

適用場景(Applicability)#

  • 許多相關類別僅在行為上有所不同,Strategy 提供一種以不同行為配置類別的方式
  • 需要同一演算法的不同變體(如不同的時間/空間取捨)
  • 演算法使用了客戶端不應知道的資料,Strategy 避免暴露複雜的演算法內部結構
  • 一個類別定義了多種行為,這些行為在操作中以多重條件判斷呈現——將各個條件分支移入對應的 Strategy 類別

結構(Structure)#

  • Strategy(如 Compositor)宣告所有演算法共用的介面
  • ConcreteStrategy(如 SimpleCompositor、TeXCompositor、ArrayCompositor)透過 Strategy 介面實作具體演算法
  • Context(如 Composition)持有一個 Strategy 物件的參照,可定義讓 Strategy 存取其資料的介面
classDiagram
    class Context {
        +ContextInterface()
    }
    class Strategy {
        <<interface>>
        +AlgorithmInterface()
    }
    class ConcreteStrategyA {
        +AlgorithmInterface()
    }
    class ConcreteStrategyB {
        +AlgorithmInterface()
    }
    class ConcreteStrategyC {
        +AlgorithmInterface()
    }
    Context o--> Strategy
    Strategy <|.. ConcreteStrategyA
    Strategy <|.. ConcreteStrategyB
    Strategy <|.. ConcreteStrategyC

參與者(Participants)#

參與者範例職責
Strategy-宣告所有支援演算法的共用介面,Context 透過此介面呼叫演算法
ConcreteStrategy-使用 Strategy 介面實作具體演算法
Context-以 ConcreteStrategy 物件進行配置;維護 Strategy 參照;可定義讓 Strategy 存取其資料的介面

協作方式(Collaborations)#

  • Strategy 和 Context 合作實現所選的演算法。Context 可在呼叫演算法時將所需資料作為參數傳入,或將自身作為引數傳入讓 Strategy 回呼
  • 客戶端建立 ConcreteStrategy 並傳給 Context,之後只與 Context 互動

優缺點(Consequences)#

優點:

  • 演算法家族:Strategy 的類別階層定義了一系列可重用的演算法或行為,繼承可抽取共同功能
  • 替代子類別化:直接子類別化 Context 會將行為硬寫進去,難以獨立變更演算法。Strategy 透過組合取代繼承,使切換、理解和擴展都更容易
  • 消除條件判斷:將不同行為封裝在各自的 Strategy 中,消除了選擇行為時的 if/switch 語句
  • 提供不同實作的選擇:客戶端可以根據時間和空間的取捨選擇不同的 Strategy

Strategy 模式的核心價值在於將演算法從使用它的類別中解耦。透過組合而非繼承來變更行為,符合「偏好組合優於繼承」的設計原則。

缺點:

  • 客戶端須了解不同 Strategy 的差異:客戶端需要理解各策略的區別才能做出選擇,可能暴露實作細節
  • Strategy 與 Context 之間的通訊開銷:統一的 Strategy 介面可能傳遞某些 ConcreteStrategy 不需要的參數,造成浪費
  • 增加物件數量:每個 Strategy 都是獨立物件。可透過將 Strategy 實作為無狀態物件讓多個 Context 共享,參見 Flyweight 模式

實作要點(Implementation)#

  • 定義 Strategy 與 Context 介面
    • 方法一:Context 將資料作為參數傳給 Strategy——解耦但可能傳遞不必要的資料
    • 方法二:Context 將自身傳入,Strategy 按需向 Context 請求資料——更精確但耦合更緊

在 C++ 中,若 Strategy 可在編譯期選定且不需執行期切換,可使用 template parameter 來配置 Context,避免抽象類別的開銷並獲得靜態綁定的效率。

  • Strategy 作為可選項:若 Context 在沒有 Strategy 時有合理的預設行為,可讓 Strategy 成為可選的。Context 檢查是否有 Strategy 物件,有則使用,無則執行預設行為。這讓客戶端在滿意預設行為時不需處理 Strategy 物件

已知應用(Known Uses)#

  • ET++InterViews:使用 Strategy 封裝不同的斷行演算法
  • RTL 系統(編譯器最佳化):Strategy 定義不同的暫存器分配方案(RegisterAllocator)和指令排程策略(RISCscheduler、CISCscheduler)
  • ET++SwapsManager:金融計算引擎,用 Strategy 物件組合不同的現金流生成、交換估值和折現因子計算方式
  • Booch 元件:使用 Strategy 作為 template 參數,支援 managed、controlled、unmanaged 三種記憶體配置策略
  • ObjectWindows:使用 Validator 物件(Strategy)封裝對話框中的資料驗證策略

相關模式(Related Patterns)#

  • Flyweight:Strategy 物件經常適合作為 Flyweight 共享