一、概述
策略模式(Strategy Pattern)是行为型设计模式之一,它定义了一系列可互换的算法,并将每个算法封装在独立的策略类中,使得算法可以在运行时根据客户端的需求进行切换。在面向对象设计中,策略模式遵循开闭原则(对扩展开放、对修改关闭),是消除条件分支语句(if-elif-else)的有力工具。
Python作为一种支持一等函数和鸭子类型的动态语言,为实现策略模式提供了独特的便利性。在Python中,我们不仅可以采用传统面向对象的方式实现策略模式,还可以利用函数、闭包、lambda表达式甚至装饰器来极大地简化策略模式的结构。本文将全面覆盖策略模式的多种实现方式,并结合排序、验证、折扣计算、文件格式处理等真实场景进行讲解。
核心思想:将算法的定义与使用分离。定义一组策略(算法族),让它们可以互相替换。策略的选择由上下文(Context)负责,客户端只需告诉上下文"我要做什么",而不需要关心"具体怎么做"。
二、经典策略模式实现
2.1 策略模式的三个角色
经典GoF策略模式由三个核心角色构成:
- Strategy(策略接口):定义所有策略类必须实现的公共接口,通常只有一个方法,如
execute() 或 run()。
- ConcreteStrategy(具体策略):实现策略接口的具体算法类,每个类封装一种算法的完整实现。
- Context(上下文):持有对策略对象的引用,维护一个策略实例,并在需要时调用策略的方法。上下文不关心策略的具体实现细节。
2.2 经典实现:支付策略示例
以下是一个经典的策略模式实现,展示不同支付方式的策略切换:
from abc import ABC, abstractmethod
from dataclasses import dataclass
class PaymentStrategy(ABC):
"""策略接口:定义所有支付策略必须实现的方法"""
def __init__(self, name: str):
self.name = name
def get_name(self) -> str:
return self.name
@abstractmethod
def pay(self, amount: float) -> None:
pass
class AlipayStrategy(PaymentStrategy):
"""具体策略A:支付宝支付"""
def __init__(self):
super().__init__("支付宝")
def pay(self, amount: float) -> None:
print(f"使用支付宝支付 {amount} 元")
# 实际的支付宝 API 调用逻辑...
class WeChatPayStrategy(PaymentStrategy):
"""具体策略B:微信支付"""
def __init__(self):
super().__init__("微信支付")
def pay(self, amount: float) -> None:
print(f"使用微信支付 {amount} 元")
class CreditCardStrategy(PaymentStrategy):
"""具体策略C:信用卡支付"""
def __init__(self, card_number: str):
super().__init__("信用卡")
self.card_number = card_number
def pay(self, amount: float) -> None:
print(f"使用信用卡({self.card_number[-4:]}) 支付 {amount} 元")
class PaymentContext:
"""上下文:持有策略对象,对外提供统一的支付接口"""
def __init__(self, strategy: PaymentStrategy):
self._strategy = strategy
def set_strategy(self, strategy: PaymentStrategy):
"""运行时切换策略"""
self._strategy = strategy
def checkout(self, amount: float):
print(f"当前支付方式: {self._strategy.get_name()}")
self._strategy.pay(amount)
# 使用示例
ctx = PaymentContext(AlipayStrategy())
ctx.checkout(99.9)
ctx.set_strategy(WeChatPayStrategy())
ctx.checkout(199.0)
ctx.set_strategy(CreditCardStrategy("6222-1234-5678-9999"))
ctx.checkout(599.0)
关键理解:上下文(PaymentContext)维护一个策略引用,提供 set_strategy() 方法允许在运行时动态替换算法。客户端只与上下文交互,不需要了解具体策略的实现细节。当需要新增支付方式时,只需新增一个策略类,无需修改现有代码。
三、函数式策略:利用Python一等函数简化
3.1 为什么可以用函数代替策略类?
在GoF经典策略模式中,每个策略被封装为一个类,其中包含一个统一命名的方法。但在Python中,函数是"一等公民"(first-class citizen),可以赋值给变量、作为参数传递、作为返回值返回。如果一个策略只需要一个方法,那么直接用函数代替策略类是完全可行的,这比创建完整的类更加简洁。
from typing import Callable, List
# 策略就是普通的可调用对象(函数)
def alipay(amount: float) -> None:
print(f"支付宝支付: {amount} 元")
def wechat_pay(amount: float) -> None:
print(f"微信支付: {amount} 元")
def credit_card(amount: float) -> None:
print(f"信用卡支付: {amount} 元")
class PaymentContext:
"""上下文:接受函数作为策略"""
def __init__(self, strategy: Callable[[float], None]):
self._strategy = strategy
def set_strategy(self, strategy: Callable[[float], None]):
self._strategy = strategy
def checkout(self, amount: float):
self._strategy(amount)
# 使用示例 - 函数作为策略
ctx = PaymentContext(alipay)
ctx.checkout(99.9)
ctx.set_strategy(wechat_pay)
ctx.checkout(199.0)
3.2 使用lambda表达式
当策略逻辑非常简单时,lambda表达式可以让代码更加精简:
# lambda 作为策略
ctx = PaymentContext(lambda amt: print(f"现金支付: {amt} 元"))
ctx.checkout(50.0)
# 更实用的例子:排序策略
numbers = [3, 1, 4, 2, 5]
numbers_sorted = sorted(numbers, key=lambda x: -x) # 降序
print(numbers_sorted) # [5, 4, 3, 2, 1]
3.3 使用闭包作为带状态的策略
如果策略需要维护内部状态(例如统计调用次数),闭包是一个很好的选择:
def make_discount_strategy(discount_rate: float):
"""创建一个折扣策略闭包,记录折扣总金额"""
total_discount = 0.0
def apply_discount(price: float) -> float:
nonlocal total_discount
discounted = price * (1 - discount_rate)
total_discount += price * discount_rate
print(f"已累计折扣: {total_discount:.2f} 元")
return discounted
return apply_discount
# 使用闭包作为折扣策略
vip_discount = make_discount_strategy(0.2)
season_discount = make_discount_strategy(0.5)
print(vip_discount(100.0)) # 80.0
print(vip_discount(200.0)) # 160.0, 累计折扣60
print(season_discount(300.0)) # 150.0
Python优势:在其他语言(如Java、C++)中,每个策略必须是一个完整的类。但在Python中,由于函数是一等公民,一个简单的函数或lambda就可以作为策略使用。这大大减少了样板代码,使策略模式在Python中的使用成本几乎为零。
四、使用Enum选择策略
枚举(Enum)提供了一种类型安全的方式来选择策略。将策略与枚举成员关联,可以有效防止传入无效的策略名称,同时利用枚举的name/value属性实现策略的自动发现和匹配。
from enum import Enum, auto
from typing import Callable, Dict
class SortStrategy(Enum):
"""排序策略枚举"""
ASC = lambda lst: sorted(lst)
DESC = lambda lst: sorted(lst, reverse=True)
BY_LENGTH = lambda lst: sorted(lst, key=len)
BY_LENGTH_DESC = lambda lst: sorted(lst, key=len, reverse=True)
class Processor:
def process(self, data: list, strategy: SortStrategy):
return strategy.value(data)
# 使用示例
p = Processor()
data = ["apple", "banana", "kiwi", "strawberry"]
print(p.process(data, SortStrategy.BY_LENGTH))
# ['kiwi', 'apple', 'banana', 'strawberry']
print(p.process(data, SortStrategy.DESC))
# ['strawberry', 'kiwi', 'banana', 'apple']
另一种常见的方式是将策略作为枚举的属性,使用字典或函数映射来关联:
from enum import Enum
class PaymentMethod(Enum):
ALIPAY = "alipay"
WECHAT = "wechat"
CREDIT_CARD = "credit_card"
CASH = "cash"
# 策略映射表:将枚举映射到具体处理函数
PAYMENT_HANDLERS = {
PaymentMethod.ALIPAY: lambda amt: print(f"支付宝 {amt}"),
PaymentMethod.WECHAT: lambda amt: print(f"微信 {amt}"),
PaymentMethod.CREDIT_CARD: lambda amt: print(f"信用卡 {amt}"),
PaymentMethod.CASH: lambda amt: print(f"现金 {amt}"),
}
def pay(method: PaymentMethod, amount: float):
handler = PAYMENT_HANDLERS.get(method)
if handler:
handler(amount)
else:
raise ValueError(f"不支持的支付方式: {method}")
# 类型安全的调用
pay(PaymentMethod.ALIPAY, 99.0)
Enum的优势:①类型安全,不会传入拼写错误的字符串;②IDE自动补全支持,编码体验好;③集中管理所有策略选项,一目了然;④可以附加元数据(如费率、显示名称等)。
五、策略注册表
5.1 基于字典的策略注册表
当策略数量较多时,使用字典作为注册表可以集中管理所有策略,避免大量的import和手动if-else判断:
from typing import Callable, Dict, Any
# 策略注册表
class StrategyRegistry:
"""通用策略注册表,支持注册和查找策略"""
def __init__(self):
self._strategies: Dict[str, Callable] = {}
def register(self, name: str, strategy: Callable):
"""注册策略"""
self._strategies[name] = strategy
def get(self, name: str) -> Callable:
"""获取策略,不存在时抛出 KeyError"""
if name not in self._strategies:
raise KeyError(f"未找到策略: {name}")
return self._strategies[name]
def list_strategies(self) -> list:
"""列出所有已注册的策略名称"""
return list(self._strategies.keys())
def execute(self, name: str, *args, **kwargs) -> Any:
"""查找并执行策略"""
strategy = self.get(name)
return strategy(*args, **kwargs)
# 使用示例
registry = StrategyRegistry()
# 注册策略
registry.register("add", lambda a, b: a + b)
registry.register("subtract", lambda a, b: a - b)
registry.register("multiply", lambda a, b: a * b)
# 执行策略
print(registry.execute("add", 10, 5)) # 15
print(registry.execute("subtract", 10, 5)) # 5
print(registry.execute("multiply", 10, 5)) # 50
print("已注册策略:", registry.list_strategies())
# 已注册策略: ['add', 'subtract', 'multiply']
5.2 使用装饰器注册策略
装饰器注册策略是一种更加Pythonic的方式,它允许在定义策略函数的同时自动完成注册,不需要单独调用 register() 方法:
from functools import wraps
from typing import Callable, Dict, Any
class StrategyRegistry:
"""支持装饰器注册的策略注册表"""
def __init__(self):
self._strategies: Dict[str, Callable] = {}
def register(self, name: str = None):
"""装饰器:将函数注册为策略"""
def decorator(func: Callable) -> Callable:
key = name or func.__name__
self._strategies[key] = func
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return decorator
def execute(self, name: str, *args, **kwargs) -> Any:
if name not in self._strategies:
raise KeyError(f"未找到策略: {name}")
return self._strategies[name](*args, **kwargs)
# 创建注册表实例
export_registry = StrategyRegistry()
# 使用装饰器注册导出策略
@export_registry.register("csv")
def export_csv(data):
print("导出为 CSV 格式...")
return "csv_content"
@export_registry.register("json")
def export_json(data):
print("导出为 JSON 格式...")
return '{"key": "value"}'
@export_registry.register("xml")
def export_xml(data):
print("导出为 XML 格式...")
return "<xml></xml>"
# 运行时根据用户选择执行不同的策略
user_choice = "json" # 假设来自用户输入
result = export_registry.execute(user_choice, "some data")
print(result)
注册表模式的优势:①策略集中管理,新增策略只需定义函数并加装饰器;②运行时按名称查找策略,消除if-elif-else链;③支持在运行时动态发现策略(如通过 list_strategies() 生成菜单);④策略的定义和注册在同一位置完成,可读性强。
六、策略模式在排序中的应用
排序是策略模式最自然的应用场景之一。Python内置的 sorted() 函数和 list.sort() 方法本身就是一个策略模式的典范——它们接受一个可选的 key 函数参数(策略)来决定排序依据。
class Sorter:
"""排序上下文,支持多种排序策略"""
def __init__(self, strategy=None):
self.strategy = strategy
def sort(self, data):
if self.strategy is None:
return sorted(data)
return sorted(data, key=self.strategy)
# 准备数据:学生列表
students = [
{"name": "Alice", "grade": 88, "age": 22},
{"name": "Bob", "grade": 95, "age": 20},
{"name": "Charlie", "grade": 78, "age": 23},
]
# 不同排序策略
by_name = lambda s: s["name"]
by_grade_desc = lambda s: -s["grade"]
by_age = lambda s: s["age"]
sorter = Sorter(by_grade_desc)
for s in sorter.sort(students):
print(s["name"], s["grade"])
# Bob 95
# Alice 88
# Charlie 78
更复杂的排序场景:多字段排序和自定义排序规则。策略模式允许我们将排序逻辑组合成可配置的管道:
from typing import List, Callable
class MultiFieldSorter:
"""多字段排序器,支持复合排序策略"""
def __init__(self, strategies: List[Callable]):
"""
strategies: 排序策略列表。
每个策略是一个 (key_func, reverse) 元组。
"""
self.strategies = strategies
def sort(self, data):
result = list(data)
for key_func, reverse in reversed(self.strategies):
result = sorted(result, key=key_func, reverse=reverse)
return result
# 先按年级降序,再按年龄升序
sorter = MultiFieldSorter([
(lambda s: s["grade"], True),
(lambda s: s["age"], False),
])
for s in sorter.sort(students):
print(s["name"], s["grade"], s["age"])
七、策略模式在验证中的应用
数据验证是策略模式的经典应用场景。不同字段可能需要不同的验证规则,而这些规则可以在运行时组合和替换:
from typing import Any, List
from dataclasses import dataclass
# 验证策略:每个策略就是一个验证函数
type Validator = Callable[[Any], str or None]
def required(value) -> str or None:
if value is None or str(value).strip() == "":
return "该字段为必填项"
return None
def min_length(min_len: int) -> Validator:
def check(value) -> str or None:
if value is not None and len(str(value)) < min_len:
return f"最少需要 {min_len} 个字符"
return None
return check
def max_length(max_len: int) -> Validator:
def check(value) -> str or None:
if value is not None and len(str(value)) > max_len:
return f"最多允许 {max_len} 个字符"
return None
return check
def is_email(value) -> str or None:
if value and "@" not in str(value):
return "请输入有效的邮箱地址"
return None
def in_range(min_val: float, max_val: float) -> Validator:
def check(value) -> str or None:
if value is not None:
try:
v = float(value)
if v < min_val or v > max_val:
return f"值必须在 {min_val} 到 {max_val} 之间"
except (TypeError, ValueError):
return "请输入有效的数字"
return None
return check
class Field:
"""带验证策略的字段定义"""
def __init__(self, name: str, validators: List[Validator]):
self.name = name
self.validators = validators
def validate(self, value) -> List[str]:
errors = []
for validator in self.validators:
error = validator(value)
if error:
errors.append(error)
return errors
class Form:
"""表单:组合多个字段的验证策略"""
def __init__(self, fields: List[Field]):
self.fields = fields
def validate(self, data: dict) -> dict:
all_errors = {}
for field in self.fields:
value = data.get(field.name)
errors = field.validate(value)
if errors:
all_errors[field.name] = errors
return all_errors
# 使用示例
registration_form = Form([
Field("username", [required, min_length(3), max_length(20)]),
Field("email", [required, is_email]),
Field("age", [required, in_range(0, 150)]),
])
user_data = {"username": "ab", "email": "not-an-email", "age": "200"}
errors = registration_form.validate(user_data)
for field, field_errors in errors.items():
print(f"{field}: {field_errors}")
# username: ['最少需要 3 个字符']
# email: ['请输入有效的邮箱地址']
# age: ['值必须在 0 到 150 之间']
验证策略的灵活性:每个验证器是一个独立的函数(策略),可以自由组合。新增验证规则只需定义一个新函数,不需要修改 Field 或 Form 类的代码。验证器本身也可以携带参数(如 min_length(3)),这是使用高阶函数实现的参数化策略。
八、策略模式在折扣计算中的应用
电商系统中的折扣计算是策略模式最常见的业务场景之一。不同用户等级、不同促销活动、不同商品类别可能适用完全不同的折扣规则:
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import List
@dataclass
class OrderItem:
name: str
price: float
quantity: int = 1
def subtotal(self) -> float:
return self.price * self.quantity
class DiscountStrategy(ABC):
"""折扣策略接口"""
@abstractmethod
def calculate(self, items: List[OrderItem]) -> float:
"""计算折扣金额(返回折扣的绝对值)"""
pass
class PercentageDiscount(DiscountStrategy):
"""按百分比折扣(如全场8折 = 20% off)"""
def __init__(self, percent: float):
self.percent = percent
def calculate(self, items: List[OrderItem]) -> float:
total = sum(item.subtotal() for item in items)
return total * (self.percent / 100.0)
class FixedAmountDiscount(DiscountStrategy):
"""固定金额折扣(如满100减20)"""
def __init__(self, amount: float, threshold: float):
self.amount = amount
self.threshold = threshold
def calculate(self, items: List[OrderItem]) -> float:
total = sum(item.subtotal() for item in items)
if total >= self.threshold:
return self.amount
return 0.0
class BuyXGetYFree(DiscountStrategy):
"""买X件送Y件"""
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def calculate(self, items: List[OrderItem]) -> float:
discount = 0.0
for item in items:
free_units = (item.quantity // self.x) * self.y
discount += free_units * item.price
return discount
class CompositeDiscount(DiscountStrategy):
"""组合折扣:同时应用多个折扣策略"""
def __init__(self, strategies: List[DiscountStrategy]):
self.strategies = strategies
def calculate(self, items: List[OrderItem]) -> float:
return sum(s.calculate(items) for s in self.strategies)
class Checkout:
"""结账上下文"""
def __init__(self, items: List[OrderItem], discount: DiscountStrategy):
self.items = items
self.discount = discount
def final_total(self) -> float:
total = sum(item.subtotal() for item in self.items)
discount_amount = self.discount.calculate(self.items)
return total - discount_amount
# 使用示例
items = [
OrderItem("Python编程书", 88.0, 2),
OrderItem("机械键盘", 399.0, 1),
OrderItem("鼠标垫", 29.0, 3),
]
# 场景1:新用户8折
c1 = Checkout(items, PercentageDiscount(20))
print(f"新用户折扣后: {c1.final_total():.2f}")
# 场景2:满500减100
c2 = Checkout(items, FixedAmountDiscount(100, 500))
print(f"满减折扣后: {c2.final_total():.2f}")
# 场景3:组合优惠(双11大促:8折 + 满500减100)
combo = CompositeDiscount([
PercentageDiscount(20),
FixedAmountDiscount(100, 500),
])
c3 = Checkout(items, combo)
print(f"组合折扣后: {c3.final_total():.2f}")
折扣策略的关键设计:①每个策略类只负责一种折扣计算逻辑,符合单一职责原则;②CompositeDiscount 演示了策略的组合模式,可以实现复杂促销规则;③新增促销类型只需新增策略类,无需修改 Checkout 类。
九、策略模式在文件格式处理中的应用
文件导入/导出功能是另一个非常适合使用策略模式的场景。不同文件格式(CSV、JSON、XML、YAML、Excel等)的解析和生成逻辑完全不同,通过策略模式可以优雅地支持多种格式:
from abc import ABC, abstractmethod
import json
import csv
import io
class FileParser(ABC):
"""文件解析策略接口"""
@abstractmethod
def parse(self, content: str) -> list:
pass
@abstractmethod
def dump(self, data: list) -> str:
pass
class JSONParser(FileParser):
def parse(self, content: str) -> list:
result = json.loads(content)
if isinstance(result, list):
return result
return [result]
def dump(self, data: list) -> str:
return json.dumps(data, ensure_ascii=False, indent=2)
class CSVParser(FileParser):
def parse(self, content: str) -> list:
reader = csv.DictReader(io.StringIO(content))
return list(reader)
def dump(self, data: list) -> str:
if not data:
return ""
output = io.StringIO()
writer = csv.DictWriter(output, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
return output.getvalue()
class FileProcessor:
"""文件处理器上下文"""
def __init__(self, parser: FileParser):
self.parser = parser
def set_parser(self, parser: FileParser):
self.parser = parser
def read(self, filepath: str) -> list:
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
return self.parser.parse(content)
def write(self, filepath: str, data: list):
content = self.parser.dump(data)
with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
# 使用时根据文件扩展名自动选择策略
def get_parser_for_extension(ext: str) -> FileParser:
parsers = {
".json": JSONParser(),
".csv": CSVParser(),
}
parser = parsers.get(ext)
if not parser:
raise ValueError(f"不支持的文件格式: {ext}")
return parser
processor = FileProcessor(JSONParser())
# processor.read("data.json") # 读取JSON文件
# processor.set_parser(get_parser_for_extension(".csv"))
# processor.write("output.csv", data) # 写入CSV文件
文件格式处理的策略优势:新的文件格式(如YAML、XML、Excel)只需新增一个策略类并注册到 get_parser_for_extension 映射表中,FileProcessor 完全不需要修改。这使得系统可以轻松扩展支持任意文件格式。
十、策略模式与命令模式对比
策略模式和命令模式(Command Pattern)在结构上很相似,但它们的意图和适用场景有所不同。理解两者的区别有助于在正确的场景选择正确的模式。
| 对比维度 |
策略模式 (Strategy) |
命令模式 (Command) |
| 核心意图 |
封装可互换的算法,解决"如何做"的问题 |
封装请求/操作,解决"做什么"的问题 |
| 粒度 |
算法级别的封装(通常多个步骤) |
操作级别的封装(可以是单个动作) |
| 状态 |
策略通常是无状态的(或通过参数传递状态) |
命令可以携带状态,支持撤销/重做 |
| 可互换性 |
强调算法互換,同一时间只有一个策略生效 |
强调操作封装和队列,可以批量执行/延迟执行 |
| 典型应用 |
排序算法、验证规则、折扣计算、支付方式 |
操作历史、宏录制、任务队列、事务管理 |
| Python中的简化 |
函数/lambda可直接替代策略类 |
可用闭包或 functools.partial 简化 |
# 命令模式示例(对比策略模式)
from dataclasses import dataclass
from typing import List
class Command(ABC):
"""命令接口(封装一个操作)"""
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class TextInsertCommand(Command):
def __init__(self, doc, text, pos):
self.doc = doc
self.text = text
self.pos = pos
def execute(self):
self.doc.insert(self.pos, self.text)
def undo(self):
self.doc.delete(self.pos, self.pos + len(self.text))
# 命令模式关注的是"操作本身"以及"如何撤销",
# 而策略模式关注的是"选择哪种算法来解决问题"。
选择指南:如果你需要让客户端选择"怎么做"——用策略模式。如果你需要记录、排队、撤销一系列"操作"——用命令模式。在Python中,如果策略/命令只有一个方法,可以直接用函数代替类。
十一、策略动态替换与组合
11.1 运行时策略热替换
策略模式的一大优势是可以在运行时动态替换策略,而无需重启应用或重新创建上下文对象。这在需要根据实时条件调整行为的场景中非常有用:
import time
from typing import Callable
class TrafficLightController:
"""交通信号灯控制器:根据时间动态切换策略"""
def __init__(self):
self._strategy = self._normal_hours
@staticmethod
def _normal_hours():
print("[平峰] 绿灯 60秒,黄灯 3秒,红灯 60秒")
@staticmethod
def _rush_hours():
print("[高峰] 绿灯 90秒,黄灯 3秒,红灯 90秒")
@staticmethod
def _midnight():
print("[深夜] 绿灯 20秒,黄灯 2秒,红灯 20秒")
def update_strategy(self):
hour = time.localtime().tm_hour
if 7 <= hour <= 9 or 17 <= hour <= 19:
self._strategy = self._rush_hours
elif hour <= 5 or hour >= 23:
self._strategy = self._midnight
else:
self._strategy = self._normal_hours
def run_cycle(self):
self.update_strategy()
self._strategy()
11.2 策略组合(Pipeline模式)
多个策略可以串联成管道(Pipeline),前一个策略的输出作为后一个策略的输入。这在数据处理场景中非常常见:
from typing import List, Callable
class DataPipeline:
"""策略管道:将多个数据转换策略串联执行"""
def __init__(self):
self.steps: List[Callable] = []
def add_step(self, strategy: Callable):
"""添加一个处理步骤"""
self.steps.append(strategy)
def remove_step(self, index: int):
"""移除指定步骤"""
if 0 <= index < len(self.steps):
self.steps.pop(index)
def process(self, data):
"""依次应用所有策略"""
result = data
for i, step in enumerate(self.steps):
print(f"步骤 {i+1}: {step.__name__}")
result = step(result)
return result
# 定义数据处理策略
def remove_empty(data: list) -> list:
return [x for x in data if x]
def remove_duplicates(data: list) -> list:
seen = set()
result = []
for x in data:
if x not in seen:
seen.add(x)
result.append(x)
return result
def normalize_case(data: list) -> list:
return [x.strip().lower() for x in data if isinstance(x, str)]
def sort_items(data: list) -> list:
return sorted(data)
# 构建处理管道
pipeline = DataPipeline()
pipeline.add_step(remove_empty)
pipeline.add_step(remove_duplicates)
pipeline.add_step(normalize_case)
pipeline.add_step(sort_items)
raw_data = ["Apple", "", "banana", "APPLE", "Cherry", "banana", ""]
clean_data = pipeline.process(raw_data)
print(clean_data)
# 步骤 1: remove_empty
# 步骤 2: remove_duplicates
# 步骤 3: normalize_case
# 步骤 4: sort_items
# ['apple', 'banana', 'cherry']
策略组合的威力:单个策略简单且专注,但通过组合可以构建出极其复杂和灵活的行为。这是策略模式从"可互换的算法"到"可编排的处理流程"的自然演变。
十二、Python中策略模式的独特优势
12.1 一等函数天然支持策略
Python的函数是一等公民,这意味着函数可以作为参数传递、作为值存储在数据结构中、作为返回值返回。这为策略模式提供了最简洁的实现路径:
# 其他语言(Java):每个策略需要一个接口 + 一个实现类
# Python:一个函数就够了
# Java风格(不推荐)
class UpperCaseFormatter:
def format(self, text): return text.upper()
class LowerCaseFormatter:
def format(self, text): return text.lower()
# Python风格(推荐)
def upper_case(text): return text.upper()
def lower_case(text): return text.lower()
# 可以作为字典的值
formatters = {
"upper": upper_case,
"lower": lower_case,
"title": lambda t: t.title(),
"capitalize": lambda t: t.capitalize(),
}
12.2 鸭子类型消除接口定义
Python的鸭子类型(Duck Typing)意味着你不需要显式定义策略接口。任何具有相同签名的可调用对象都可以作为策略使用。这消除了GoF策略模式中最正式的"策略接口"角色:
# 不需要统一的策略接口!任何接受相同参数、返回相同类型的可调用对象都能工作
class StrategyUser:
def __init__(self, strategy):
self.strategy = strategy # 任何可调用对象
def execute(self, x, y):
return self.strategy(x, y)
# 以下全部合法:
StrategyUser(lambda x, y: x + y) # lambda
StrategyUser(lambda x, y: x * y) # 另一个 lambda
def my_func(a, b): return a - b
StrategyUser(my_func) # 普通函数
class Adder:
def __call__(self, x, y): return x + y
StrategyUser(Adder()) # 可调用对象
import operator
StrategyUser(operator.add) # 模块中的函数
StrategyUser(operator.mul) # 模块中的函数
12.3 functools.partial 预配置策略
functools.partial 可以冻结函数的部分参数,生成预配置的策略变体:
from functools import partial
def format_with_prefix(prefix, text, suffix):
return f"{prefix}{text}{suffix}"
# 使用 partial 预配置参数,生成不同策略
html_wrap = partial(format_with_prefix, "<b>", suffix="</b>")
json_wrap = partial(format_with_prefix, '{"value": "', suffix='"}')
print(html_wrap("Hello")) # <b>Hello</b>
print(json_wrap("Hello")) # {"value": "Hello"}
Python策略模式的核心优势:①函数是一等公民——策略可以就是普通函数;②鸭子类型——不需要显式接口定义;③partial预配置——从通用函数派生专用策略;④装饰器注册——声明式策略管理;⑤动态类型——同一上下文可接受不同类型的策略。
十三、策略模式性能考量
13.1 策略创建开销
在性能敏感的场景中,策略对象的创建和销毁成本需要被考虑:
- 函数策略优于类策略:函数的创建和调用开销远小于类的实例化,尤其在短生命周期策略中差异明显。
- 复用策略对象:无状态策略可以声明为模块级别的单例,避免重复创建。
- 延迟初始化:在需要时才创建策略对象,避免一次性创建所有策略。
# 策略复用:无状态策略声明为模块级别常量
class UpperStrategy:
def transform(self, text):
return text.upper()
# 单例复用(无状态策略)
UPPER_STRATEGY = UpperStrategy()
LOWER_STRATEGY = LowerStrategy()
# 策略池:预创建常用策略
class StrategyPool:
_strategies = {}
@classmethod
def get(cls, key):
if key not in cls._strategies:
cls._strategies[key] = cls._create(key)
return cls._strategies[key]
@classmethod
def _create(cls, key):
# 延迟创建策略
...
13.2 策略查找性能
当策略数量较多时,策略查找可能成为性能瓶颈:
- 字典查找(O(1)):策略注册表使用dict存储,查找速度恒定,适合策略数量较多的场景。
- if-elif 链(O(n)):策略数量增多时线性变慢,同时代码可读性差。
- Enum + Dict:结合枚举的类型安全和字典的性能,是推荐组合。
# 性能对比:字典查找 vs if-elif
import timeit
strategies = {f"s{i}": lambda x: x for i in range(100)}
def dict_lookup(key):
return strategies.get(key, lambda x: None)
def if_elif(key):
if key == "s0": return lambda x: x
elif key == "s1": return lambda x: x
# ... 100个elif
else: return lambda x: None
# 字典查找在策略数量大时优势明显
# dict_lookup 是 O(1),if_elif 是 O(n)
性能建议:在绝大多数业务应用中,策略模式的性能开销可以忽略不计。优先关注代码的可维护性和可读性,只有在明确识别到性能瓶颈(如每秒钟调用策略数十万次)时才进行优化。
十四、核心要点总结
一、模式本质:策略模式将"算法的定义"与"算法的使用"分离,通过组合代替继承来实现行为的动态配置。核心是"针对接口编程,而非针对实现编程"。
二、三种实现层次:①经典面向对象方式(接口+策略类+上下文)——适合复杂策略和需要额外状态维护的场景;②函数式方式(函数/lambda/闭包)——适合简单策略,代码量最小;③注册表方式(字典/装饰器注册)——适合策略数量多、需要集中管理和按名称查找的场景。
三、Python独特优势:一等函数、鸭子类型、functools.partial、装饰器、动态类型等语言特性使策略模式的实现比传统静态语言更加简洁灵活。
四、常见应用场景:排序算法选择、数据验证规则组合、折扣计算策略、支付方式切换、文件格式解析/导出、数据清洗管道、路由分发、AI模型选择等。
五、与命令模式的区别:策略模式解决"算法互换"问题(怎么做),命令模式解决"操作封装"问题(做什么)。策略通常无状态,命令通常持有状态并支持撤销。
六、设计原则:①开闭原则——新增策略不修改现有代码;②单一职责——每个策略类只负责一种算法;③依赖倒置——上下文依赖抽象策略接口而非具体实现。
七、性能考量:无状态策略应复用为单例;策略数量多时使用字典注册表(O(1)查找);函数策略比类策略开销更低。
十五、进一步思考
策略模式是设计模式中"从继承到组合"思想转变的典范。在学习了策略模式的基本实现后,可以进一步思考以下问题:
- 与状态模式的关系:状态模式可以看作是策略模式的一种特例——在状态模式中,策略的切换通常由上下文的状态变化驱动,而非由客户端主动选择。
- 策略的元数据:除了算法本身,策略是否可以携带元数据(如策略名称、优先级、适用范围)?这可以通过在策略上添加属性或使用dataclass实现。
- 异步策略:在异步编程中,策略可以是协程函数(async def),上下文通过 await 调用策略。这为高并发场景下的策略模式提供了新的可能性。
- 策略的自动化选择:结合机器学习或规则引擎,可以让系统自动选择最优策略,而非由客户端手动指定。
- 策略的监控与度量:在生产环境中,可以为每个策略添加执行时间、成功率等监控指标,帮助运维人员了解各策略的表现。
扩展阅读:GoF《设计模式》中的Strategy模式;《Python设计模式》第7章;Python官方文档中关于 functools.partial 和 abc.ABC 的部分;Wikipedia上关于Strategy pattern与其他行为型模式的对比。
最佳实践:在Python项目中,优先考虑使用函数作为策略的实现方式,只在策略需要维护复杂状态或需要多个方法时才使用类。善用 functools.partial 从通用函数派生出专用策略。使用装饰器和注册表模式实现策略的集中管理。在团队项目中,统一策略的命名规范和接口约定。