策略模式(Strategy Pattern)详解
一、策略模式的定义
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一组算法,将每个算法封装起来,并使它们可以相互替换,从而让算法的变化独立于使用它的客户(Client)。
换句话说,策略模式让一个类的行为或其算法可以在运行时更改,而不会影响使用该类的代码。
二、生活中的类比
想象一下,你去一家披萨店点披萨,他们提供了三种不同的切割方式:
- 正常切割(切成八块)
- 方块切割(切成小方块)
- 不切割(整块送上)
每种切割方式是一个策略,你可以在点餐时选择适合自己的方式。这意味着:
- 披萨店(上下文 ,Context)不关心具体的切割方法,只需要使用一个策略接口来执行对应的切割方式。
- 你可以随时更换切割策略,而不需要修改披萨店的代码。
三、策略模式的结构
策略模式主要由 3 个核心部分 组成:
组件 | 作用 |
---|---|
策略接口(Strategy) | 定义一组可以互相替换的算法接口。 |
具体策略(Concrete Strategy) | 实现策略接口的不同算法。 |
上下文(Context) | 负责与策略接口交互,并可以动态切换策略。 |
UML 类图
urm
+----------------------+
| Context |
|----------------------|
| strategy: Strategy | ----> +----------------------+
|----------------------| | Strategy |
| setStrategy() | |----------------------|
| executeStrategy() | | execute() |
+----------------------+ +----------------------+
| ▲
| |
| +----------------------------------------+
| | | |
+--------------------+ +--------------------+ +--------------------+
| ConcreteStrategyA | | ConcreteStrategyB | | ConcreteStrategyC |
|--------------------| |--------------------| |--------------------|
| execute() | | execute() | | execute() |
+--------------------+ +--------------------+ +--------------------+
四、策略模式的 Python 实现
(1) 定义策略接口
python
from abc import ABC, abstractmethod
# 1. 定义策略接口(抽象策略)
class Strategy(ABC):
@abstractmethod
def execute(self, a, b):
pass
(2) 实现具体策略
python
# 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) 创建上下文并动态切换策略
python
# 5. 上下文类
class Context:
def __init__(self, strategy: Strategy):
self._strategy = strategy # 初始化时指定策略
def set_strategy(self, strategy: Strategy):
"""动态更改策略"""
self._strategy = strategy
def execute_strategy(self, a, b):
"""执行策略"""
return self._strategy.execute(a, b)
(4) 测试策略模式
bash
# 客户端代码
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) 输出结果
ini
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
里写多个方法,而不是用多个策略类:
ruby
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
然后调用:
scss
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。 - 如果使用策略模式 ,你只需新增一个策略类,而不需要改动原来的代码。
示例:
-
传统方式:
rubyclass Calculator: def add(self, a, b): return a + b def subtract(self, a, b): return a - b def multiply(self, a, b): return a * b def divide(self, a, b): return a / b # 这里修改了原始类
问题:
- 你不得不改动
Calculator
,如果这个类被很多地方用到,可能影响其他代码。 - 代码耦合度变高,修改的风险变大。
- 你不得不改动
-
策略模式:
rubyclass DivideStrategy(Strategy): def execute(self, a, b): return a / b # 只新增类,不改动原有代码
好处:
- 旧代码不需要改动,减少出错的风险!
- 代码更模块化 ,每个策略独立,更易扩展。
(2) 让代码更清晰,避免复杂的 if-else
如果不使用策略模式,你可能会写出大量的 if-else
语句:
kotlin
class Calculator:
def execute(self, strategy, a, b):
if strategy == "add":
return a + b
elif strategy == "subtract":
return a - b
elif strategy == "multiply":
return a * b
else:
raise ValueError("Unknown strategy")
问题:
- 随着策略增加,
if-else
会变得越来越长,可读性下降。 - 每次增加新策略,都要改
execute
方法,代码耦合度高。
而使用策略模式后,代码变得清晰且易维护:
ini
context = Context(AddStrategy()) # 直接使用策略对象
result = context.execute_strategy(10, 5) # 运行时切换策略
- 没有
if-else
,可读性更强! - 新增算法时,不需要改
Context
,只需添加新的Strategy
类。
(3) 运行时动态切换策略
假设你在运行时需要更换算法:
- 单类方案 :你必须写
if-else
逻辑,或者调用不同的方法。 - 策略模式 :你可以动态传入 不同的策略对象,无需
if-else
。
示例:
ini
context = Context(AddStrategy()) # 初始使用加法
context.execute_strategy(10, 5) # 10 + 5 = 15
context.set_strategy(SubtractStrategy()) # 运行时切换成减法
context.execute_strategy(10, 5) # 10 - 5 = 5
- 运行时随意切换算法 ,而不需要
if-else
。 - 新增算法时不影响原代码,降低耦合度。
(4) 代码更容易测试
- 单类方案 :如果
Calculator
里包含 10+ 种运算方法,每次测试它,你需要测试整个Calculator
类,容易受其他方法影响。 - 策略模式 :每个策略独立,你可以单独测试 每个
Strategy
类,不受其他策略影响。
示例(使用 PyTest 单测):
scss
def test_add_strategy():
strategy = AddStrategy()
assert strategy.execute(10, 5) == 15
def test_subtract_strategy():
strategy = SubtractStrategy()
assert strategy.execute(10, 5) == 5
- 单独测试每个策略 ,而不是整个
Calculator
类。 - 减少依赖关系,让测试更清晰。
(5) 代码更符合 SOLID 原则
设计原则 | 策略模式如何符合 |
---|---|
单一职责原则(SRP) | 每个 Strategy 只负责一个算法,职责清晰。 |
开闭原则(OCP) | 可以新增策略,而不修改原代码。 |
依赖倒置原则(DIP) | Context 依赖于 Strategy 接口,而不是具体实现。 |
总结
方案 | 优势 | 劣势 |
---|---|---|
单类 + 多方法 | 代码短、简单 | 不符合开闭原则,难以扩展,难以测试,耦合度高 |
策略模式 | 符合 SOLID 原则、易扩展、易测试 | 初期代码量较多 |
什么时候该用策略模式?
✅ 当你有多个可变的算法(如支付方式、排序方法)时,策略模式是最佳选择!
❌ 如果你的方法固定、不会变化,直接写在一个类里更简单。
一句话总结
👉 如果你的代码需要多个可替换的算法,并且希望避免 if-else
代码膨胀,策略模式就是最好的选择! 🎯