意圖(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 共享