設計模式(二十二)行為型:策略模式詳解
策略模式(Strategy Pattern)是 GoF 23 種設計模式中最具實用性和廣泛影響力的行為型模式之一,其核心價值在于定義一系列算法或行為,并將每個算法封裝到獨立的類中,使得它們可以相互替換,且算法的變化獨立于使用它的客戶端。它通過將“算法”與“使用算法的上下文”解耦,實現了行為的動態配置與高度可擴展性。策略模式是構建可配置系統、實現多態行為、支持插件化架構、優化性能選擇、實現業務規則引擎、支持 A/B 測試等場景的基石,是將“算法即服務”理念落地的關鍵設計范式。
一、詳細介紹
策略模式解決的是“一個類有多種實現同一功能的算法,且這些算法需要在運行時根據條件動態選擇,或需要獨立于客戶端進行擴展和維護”的問題。在傳統設計中,通常使用條件語句(如 if-else
或 switch-case
)根據配置或參數選擇不同的算法分支。這導致:
- 代碼臃腫:所有算法邏輯集中在單一方法或類中。
- 難以擴展:新增算法需要修改現有代碼,違反開閉原則。
- 難以復用:算法邏輯無法獨立復用。
- 緊耦合:上下文類與具體算法實現緊密耦合。
策略模式的核心思想是:將每個算法封裝成一個獨立的類(策略類),這些類實現一個共同的策略接口。上下文類(Context)持有對策略接口的引用,通過多態調用算法,而無需知道具體實現。算法的切換通過注入不同的策略對象實現。
該模式包含以下核心角色:
- Context(上下文):定義客戶端使用的接口,包含一個對
Strategy
接口的引用。它將算法相關的請求委托給策略對象執行,而不關心具體實現。 - Strategy(策略接口):定義所有具體策略類共有的操作接口(如
execute()
、calculate()
)。它聲明了算法的抽象行為。 - ConcreteStrategyA, ConcreteStrategyB, …(具體策略類):實現
Strategy
接口,封裝具體的算法或行為實現。每個具體策略提供一種算法的完整實現。
策略模式的關鍵優勢:
- 符合開閉原則:新增算法只需添加新的具體策略類,無需修改上下文或客戶端代碼。
- 符合單一職責原則:每個策略類只負責一種算法的實現。
- 算法可獨立復用:策略類可被多個上下文或系統復用。
- 支持運行時切換:上下文可在運行時動態更換策略對象。
- 消除條件語句:將
switch
邏輯轉化為對象組合。 - 易于單元測試:每個策略可獨立測試。
與“狀態模式”相比,策略模式關注算法的替換,狀態模式關注狀態驅動的行為變化;策略通常由客戶端或配置決定,狀態轉換由內部邏輯驅動。與“命令模式”相比,命令封裝請求,策略封裝算法。與“模板方法模式”相比,模板方法在編譯時通過繼承固定算法骨架,策略在運行時通過組合動態選擇算法。
策略模式適用于:
- 多種算法實現同一功能(如排序、壓縮、加密、支付方式、運費計算)。
- 需要運行時動態選擇算法。
- 需要避免龐大的條件分支。
- 需要獨立測試或復用算法。
- 實現業務規則引擎或配置化行為。
二、策略模式的UML表示
以下是策略模式的標準 UML 類圖:
圖解說明:
Context
持有對Strategy
接口的引用。Context
通過setStrategy()
接受不同的策略實現。Context
的executeStrategy()
方法將調用委托給當前Strategy
的execute()
。ConcreteStrategy
實現execute()
,提供具體算法。- 客戶端通過向
Context
注入不同的ConcreteStrategy
來改變其行為。
三、一個簡單的Java程序實例及其UML圖
以下是一個電商系統中運費計算的示例,支持多種運費計算策略(標準、加急、免費)。
Java 程序實例
// 策略接口:運費計算器
interface ShippingCostStrategy {double calculate(double weight, double distance);
}// 具體策略:標準運費
class StandardShippingStrategy implements ShippingCostStrategy {private static final double RATE_PER_KG = 2.5;private static final double RATE_PER_KM = 0.1;@Overridepublic double calculate(double weight, double distance) {double cost = (weight * RATE_PER_KG) + (distance * RATE_PER_KM);System.out.println("📦 標準運費: " + weight + "kg × $" + RATE_PER_KG + "/kg + " + distance + "km × $" + RATE_PER_KM + "/km = $" + cost);return cost;}
}// 具體策略:加急運費
class ExpressShippingStrategy implements ShippingCostStrategy {private static final double BASE_FEE = 15.0;private static final double PREMIUM_RATE = 0.5; // per km@Overridepublic double calculate(double weight, double distance) {double cost = BASE_FEE + (distance * PREMIUM_RATE);// 重量影響較小,主要按距離和基礎費System.out.println("? 加急運費: 基礎費 $" + BASE_FEE + " + " + distance + "km × $" + PREMIUM_RATE + "/km = $" + cost);return cost;}
}// 具體策略:免費運費(促銷活動)
class FreeShippingStrategy implements ShippingCostStrategy {@Overridepublic double calculate(double weight, double distance) {System.out.println("🎁 免費運費: 訂單滿足促銷條件,運費為 $0.00");return 0.0;}
}// 上下文:購物車
class ShoppingCart {private double totalAmount;private double weight;private double distance;private ShippingCostStrategy shippingStrategy; // 持有策略引用public ShoppingCart(double totalAmount, double weight, double distance) {this.totalAmount = totalAmount;this.weight = weight;this.distance = distance;// 默認策略:標準運費this.shippingStrategy = new StandardShippingStrategy();System.out.println("🛒 購物車創建: 金額=$" + totalAmount + ", 重量=" + weight + "kg, 距離=" + distance + "km");}// 動態設置運費策略public void setShippingStrategy(ShippingCostStrategy strategy) {this.shippingStrategy = strategy;System.out.println("🔄 運費策略已切換");}// 計算總費用(商品金額 + 運費)public double calculateTotal() {double shippingCost = shippingStrategy.calculate(weight, distance);double total = totalAmount + shippingCost;System.out.println("💰 訂單總費用: $" + totalAmount + " + $" + shippingCost + " = $" + total);return total;}// 根據訂單金額自動應用免費運費(演示策略選擇邏輯)public void applyPromotion() {if (totalAmount >= 100.0) {System.out.println("🎉 訂單金額 ≥ $100,應用免費運費促銷!");setShippingStrategy(new FreeShippingStrategy());} else {System.out.println("🛒 訂單金額 < $100,不滿足免費運費條件。");}}
}// 客戶端使用示例
public class StrategyPatternDemo {public static void main(String[] args) {System.out.println("🛒 電商運費計算系統 - 策略模式示例\n");// 場景1: 普通訂單,使用標準運費System.out.println("--- 場景1: 普通訂單 ($80) ---");ShoppingCart cart1 = new ShoppingCart(80.0, 5.0, 200.0);cart1.calculateTotal(); // 使用默認標準策略System.out.println("\n--- 切換為加急運費 ---");cart1.setShippingStrategy(new ExpressShippingStrategy());cart1.calculateTotal();// 場景2: 大額訂單,應用促銷System.out.println("\n\n--- 場景2: 大額訂單 ($120) ---");ShoppingCart cart2 = new ShoppingCart(120.0, 3.0, 150.0);cart2.applyPromotion(); // 自動應用免費運費cart2.calculateTotal();System.out.println("\n--- 手動切換回標準運費(演示靈活性)---");cart2.setShippingStrategy(new StandardShippingStrategy());cart2.calculateTotal();}
}
實例對應的UML圖(簡化版)
運行說明:
ShoppingCart
是上下文,持有ShippingCostStrategy
引用。ShippingCostStrategy
定義運費計算接口。- 三種具體策略實現不同的計算邏輯。
ShoppingCart
通過setShippingStrategy()
動態更換策略。calculateTotal()
委托給當前策略的calculate()
方法。applyPromotion()
演示了根據業務規則自動選擇策略。
四、總結
特性 | 說明 |
---|---|
核心目的 | 封裝可互換的算法,實現算法與使用的解耦 |
實現機制 | 定義策略接口,上下文委托調用具體策略 |
優點 | 符合開閉/單一職責原則、消除條件分支、支持運行時切換、算法可復用、易于測試 |
缺點 | 增加類數量、客戶端需了解策略類型、簡單算法可能過度設計 |
適用場景 | 多種算法選擇、配置化行為、插件化架構、業務規則引擎、A/B測試 |
不適用場景 | 算法極少、行為固定、性能極度敏感(虛函數調用開銷) |
策略模式使用建議:
- 策略類可設計為無狀態(推薦),便于共享和線程安全。
- 可結合“工廠模式”或“依賴注入”創建和注入策略。
- 在 Java 中,
enum
可實現簡單策略(每個常量實現接口)。 - 可結合“組合模式”實現復合策略。
架構師洞見:
策略模式是“關注點分離”與“依賴倒置”原則的典范。在現代架構中,其思想已演變為微服務架構(每個服務是業務策略的實現)、插件系統(如 IDE 插件、瀏覽器擴展)、A/B 測試框架(不同策略對應不同用戶組)、機器學習模型服務(不同模型作為預測策略)和 云原生配置管理(不同環境使用不同策略)。例如,Spring 框架的PasswordEncoder
、LoadBalancer
都是策略模式的應用;Kubernetes 的調度器可選擇不同調度策略;在 AI 推理服務中,不同模型版本作為策略被動態切換。未來趨勢是:策略模式將與Serverless 架構深度融合,每個函數即一個策略;在邊緣計算中,設備根據網絡狀況選擇數據處理策略;在量子計算中,不同量子算法作為策略被選擇;在元宇宙中,用戶交互邏輯可配置為不同策略。
掌握策略模式,是設計靈活、可配置、可演進系統的必備技能。作為架構師,應在設計任何需要“多選項”、“可配置行為”或“算法替換”的模塊時,優先考慮策略模式。它不僅是模式,更是系統適應性的靈魂——它讓系統擺脫了硬編碼的束縛,具備了在變化的環境中動態選擇最優路徑的能力,從而構建出能夠持續進化、自我優化的智能軟件生態系統。