策略模式(Strategy Pattern)詳解
一、策略模式的定義
策略模式(Strategy Pattern)是一種行為型設計模式,它定義了一組算法,將每個算法封裝起來,并使它們可以相互替換,從而讓算法的變化獨立于使用它的客戶(Client)。
換句話說,策略模式讓一個類的行為或其算法可以在運行時更改,而不會影響使用該類的代碼。
二、生活中的類比
想象一下,你去一家披薩店點披薩,他們提供了三種不同的切割方式:
- 正常切割(切成八塊)
- 方塊切割(切成小方塊)
- 不切割(整塊送上)
每種切割方式是一個策略,你可以在點餐時選擇適合自己的方式。這意味著:
- 披薩店(上下文,Context)不關心具體的切割方法,只需要使用一個策略接口來執行對應的切割方式。
- 你可以隨時更換切割策略,而不需要修改披薩店的代碼。
三、策略模式的結構
策略模式主要由 3 個核心部分 組成:
組件 | 作用 |
---|---|
策略接口(Strategy) | 定義一組可以互相替換的算法接口。 |
具體策略(Concrete Strategy) | 實現策略接口的不同算法。 |
上下文(Context) | 負責與策略接口交互,并可以動態切換策略。 |
UML 類圖
+----------------------+| Context | |----------------------|| strategy: Strategy | ----> +----------------------+|----------------------| | Strategy | | setStrategy() | |----------------------|| executeStrategy() | | execute() |+----------------------+ +----------------------+| ▲| || +----------------------------------------+| | | |+--------------------+ +--------------------+ +--------------------+| ConcreteStrategyA | | ConcreteStrategyB | | ConcreteStrategyC ||--------------------| |--------------------| |--------------------|| execute() | | execute() | | execute() |+--------------------+ +--------------------+ +--------------------+
四、策略模式的 Python 實現
(1) 定義策略接口
from abc import ABC, abstractmethod# 1. 定義策略接口(抽象策略)
class Strategy(ABC):@abstractmethoddef execute(self, a, b):pass
(2) 實現具體策略
# 2. 具體策略A:加法
class AddStrategy(Strategy):def execute(self, a, b):return a + b# 3. 具體策略B:減法
class SubtractStrategy(Strategy):def execute(self, a, b):return a - b# 4. 具體策略C:乘法
class MultiplyStrategy(Strategy):def execute(self, a, b):return a * b
(3) 創建上下文并動態切換策略
# 5. 上下文類
class Context:def __init__(self, strategy: Strategy):self._strategy = strategy # 初始化時指定策略def set_strategy(self, strategy: Strategy):"""動態更改策略"""self._strategy = strategydef execute_strategy(self, a, b):"""執行策略"""return self._strategy.execute(a, b)
(4) 測試策略模式
# 客戶端代碼
context = Context(AddStrategy()) # 初始使用加法策略
print("10 + 5 =", context.execute_strategy(10, 5))context.set_strategy(SubtractStrategy()) # 切換為減法策略
print("10 - 5 =", context.execute_strategy(10, 5))context.set_strategy(MultiplyStrategy()) # 切換為乘法策略
print("10 * 5 =", context.execute_strategy(10, 5))
(5) 輸出結果
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
五、策略模式的優缺點
? 優點
- 符合開閉原則(Open-Closed Principle):可以新增新的策略,而不影響已有代碼。
- 避免冗長的
if-else
語句:如果不使用策略模式,可能會有大量if-else
邏輯判斷。 - 支持動態切換:可以在運行時自由更換不同的算法,而不修改原有代碼。
? 缺點
- 增加了代碼復雜度:每個策略都需要定義一個類,當策略過多時,會導致類爆炸。
- 客戶端需要了解不同策略:調用方需要知道有哪些可用策略,才能正確選擇。
六、策略模式的應用場景
? (1) 算法的可替換性
- 計算稅收(不同國家稅率)
- 支付方式(微信支付、支付寶、銀行卡)
? (2) 處理不同格式的文件
- 解析 JSON、XML、CSV 文件時,使用不同的解析策略。
? (3) AI/機器學習中的優化策略
- 訓練模型時,可以使用不同的優化算法(SGD、Adam、RMSprop)。
七、策略模式 vs 其他模式
設計模式 | 作用 | 適用場景 |
---|---|---|
策略模式 | 允許在運行時更換不同算法。 | 當有多種算法可選,且希望避免 if-else 邏輯。 |
狀態模式 | 允許對象在不同狀態下表現出不同行為。 | 對象的行為依賴于狀態變化,例如訂單狀態。 |
工廠模式 | 負責創建對象,但不指定其具體實現。 | 需要動態創建不同策略對象時,可結合策略模式使用。 |
八、總結
- 策略模式核心思想: 定義一組算法,讓它們可以互相替換,而不會影響客戶端代碼。
- 適用于: 需要多個可替換算法,并希望避免
if-else
邏輯的場景。 - 優點: 符合開閉原則,可動態切換策略,避免冗長
if-else
。 - 缺點: 可能會增加類的數量,使代碼更復雜。
為什么不用一個類,而是用策略模式?
你提的這個問題很關鍵!乍一看,把不同的算法方法寫在同一個類里,確實可以實現同樣的功能。比如,我們可以直接在 Context
里寫多個方法,而不是用多個策略類:
class Calculator:def add(self, a, b):return a + bdef subtract(self, a, b):return a - bdef multiply(self, a, b):return a * b
然后調用:
calc = Calculator()
print(calc.add(10, 5))
print(calc.subtract(10, 5))
print(calc.multiply(10, 5))
這看起來代碼更簡單,那么為什么我們還要使用策略模式呢?🤔
策略模式 vs. 在同一個類中寫多個方法
方式 | 優勢 | 劣勢 |
---|---|---|
把所有方法寫在一個類里 | 代碼量少、容易理解 | 不符合開閉原則,修改或添加算法時要改原始代碼,不利于擴展 |
使用策略模式(多個類) | 代碼更靈活、符合開閉原則,更易擴展 | 初期代碼量較多,需要額外的類 |
五大核心原因:為什么策略模式更優?
(1) 符合 “開閉原則”(OCP)
開閉原則(Open-Closed Principle,OCP):軟件應該對擴展開放,對修改關閉。
假設你的 Calculator
需要增加 除法、取余、指數運算、平方根 等方法,那么:
- 如果所有方法都寫在
Calculator
里,你必須不斷修改它,破壞 OCP。 - 如果使用策略模式,你只需新增一個策略類,而不需要改動原來的代碼。
示例:
-
傳統方式:
class Calculator:def add(self, a, b): return a + bdef subtract(self, a, b): return a - bdef multiply(self, a, b): return a * bdef divide(self, a, b): return a / b # 這里修改了原始類
問題:
- 你不得不改動
Calculator
,如果這個類被很多地方用到,可能影響其他代碼。 - 代碼耦合度變高,修改的風險變大。
- 你不得不改動
-
策略模式:
class DivideStrategy(Strategy):def execute(self, a, b): return a / b # 只新增類,不改動原有代碼
好處:
- 舊代碼不需要改動,減少出錯的風險!
- 代碼更模塊化,每個策略獨立,更易擴展。
(2) 讓代碼更清晰,避免復雜的 if-else
如果不使用策略模式,你可能會寫出大量的 if-else
語句:
class Calculator:def execute(self, strategy, a, b):if strategy == "add":return a + belif strategy == "subtract":return a - belif strategy == "multiply":return a * belse:raise ValueError("Unknown strategy")
問題:
- 隨著策略增加,
if-else
會變得越來越長,可讀性下降。 - 每次增加新策略,都要改
execute
方法,代碼耦合度高。
而使用策略模式后,代碼變得清晰且易維護:
context = Context(AddStrategy()) # 直接使用策略對象
result = context.execute_strategy(10, 5) # 運行時切換策略
- 沒有
if-else
,可讀性更強! - 新增算法時,不需要改
Context
,只需添加新的Strategy
類。
(3) 運行時動態切換策略
假設你在運行時需要更換算法:
- 單類方案:你必須寫
if-else
邏輯,或者調用不同的方法。 - 策略模式:你可以動態傳入不同的策略對象,無需
if-else
。
示例:
context = Context(AddStrategy()) # 初始使用加法
context.execute_strategy(10, 5) # 10 + 5 = 15context.set_strategy(SubtractStrategy()) # 運行時切換成減法
context.execute_strategy(10, 5) # 10 - 5 = 5
- 運行時隨意切換算法,而不需要
if-else
。 - 新增算法時不影響原代碼,降低耦合度。
(4) 代碼更容易測試
- 單類方案:如果
Calculator
里包含 10+ 種運算方法,每次測試它,你需要測試整個Calculator
類,容易受其他方法影響。 - 策略模式:每個策略獨立,你可以單獨測試每個
Strategy
類,不受其他策略影響。
示例(使用 PyTest 單測):
def test_add_strategy():strategy = AddStrategy()assert strategy.execute(10, 5) == 15def test_subtract_strategy():strategy = SubtractStrategy()assert strategy.execute(10, 5) == 5
- 單獨測試每個策略,而不是整個
Calculator
類。 - 減少依賴關系,讓測試更清晰。
(5) 代碼更符合 SOLID 原則
設計原則 | 策略模式如何符合 |
---|---|
單一職責原則(SRP) | 每個 Strategy 只負責一個算法,職責清晰。 |
開閉原則(OCP) | 可以新增策略,而不修改原代碼。 |
依賴倒置原則(DIP) | Context 依賴于 Strategy 接口,而不是具體實現。 |
總結
方案 | 優勢 | 劣勢 |
---|---|---|
單類 + 多方法 | 代碼短、簡單 | 不符合開閉原則,難以擴展,難以測試,耦合度高 |
策略模式 | 符合 SOLID 原則、易擴展、易測試 | 初期代碼量較多 |
什么時候該用策略模式?
? 當你有多個可變的算法(如支付方式、排序方法)時,策略模式是最佳選擇!
? 如果你的方法固定、不會變化,直接寫在一個類里更簡單。
一句話總結
👉 如果你的代碼需要多個可替換的算法,并且希望避免 if-else
代碼膨脹,策略模式就是最好的選擇! 🎯