Python策略模式:从入门到实战
前言
写代码写久了,你肯定遇到过这种情况:一个功能有好几种实现方式,然后代码里堆满了 if-else。刚开始还好,后面需求一加,改起来简直要命。策略模式就是专门治这个病的。
说白了,策略模式就是把不同的"做法"分别封装起来,用的时候想换就换。Python 写这个特别顺手,毕竟函数可以直接当参数传,比 Java 那套写法省事多了。这篇文章就带你把策略模式从概念到实战都过一遍。
🏠个人主页:你的主页
文章目录
- Python策略模式:从入门到实战
- 一、策略模式概念
- 二、不使用策略模式的痛点
- 三、Python实现策略模式的多种方式
- [四、Python vs Java策略模式对比](#四、Python vs Java策略模式对比)
- 五、FastAPI实战:构建支付服务
- 六、策略模式使用场景与注意事项
- 七、总结
一、策略模式概念
策略模式是一种行为设计模式,核心思想就是把不同的算法封装成独立的类,让它们可以互相替换。
举个接地气的例子,你每天上班可以:
- 开车 --- 快但可能堵
- 坐地铁 --- 稳但要走路
- 骑车 --- 锻炼身体但累
目的都一样,就是到公司,但具体怎么去各有各的逻辑。策略模式干的就是这事:把这些不同的"去法"各自封装好,到时候想用哪个用哪个。
策略模式一般包含三个角色:
- 策略接口:定义统一的方法签名
- 具体策略类:实现具体算法
- 上下文类:持有策略对象,负责调用
其实概念挺简单的,关键是看代码怎么写。
二、不使用策略模式的痛点
先看看不用策略模式会写成什么样:
python
# 典型的 if-else 地狱
class PaymentProcessor:
def pay(self, method: str, amount: float):
if method == "alipay":
print(f"调用支付宝SDK...")
print(f"支付宝支付 {amount} 元")
# 一堆支付宝的逻辑...
elif method == "wechat":
print(f"调用微信SDK...")
print(f"微信支付 {amount} 元")
# 一堆微信的逻辑...
elif method == "credit_card":
print(f"调用银行接口...")
print(f"信用卡支付 {amount} 元")
# 一堆信用卡的逻辑...
elif method == "apple_pay":
# 又来一个...
pass
else:
raise ValueError(f"不支持的支付方式: {method}")
这种代码的问题太明显了:
首先,每次加新支付方式都得改这个类,万一手抖改错了别的分支呢?其次,这个方法会越来越长,几百行代码挤一块,看着就头疼。再者,测试也麻烦,你想测支付宝的逻辑,结果整个类都得跑一遍。
刚开始写可能觉得这样直接,但业务一复杂,维护成本蹭蹭往上涨。
三、Python实现策略模式的多种方式
Python 实现策略模式花样挺多的,下面从正经到花哨都讲一遍。
3.1 基于抽象基类的经典实现
这是最规矩的写法,结构清晰,适合团队协作:
python
from abc import ABC, abstractmethod
from typing import Optional
# 1. 定义策略接口
class PaymentStrategy(ABC):
"""支付策略的抽象基类"""
@abstractmethod
def pay(self, amount: float) -> bool:
"""执行支付"""
pass
@abstractmethod
def get_name(self) -> str:
"""获取支付方式名称"""
pass
# 2. 具体策略实现
class AlipayStrategy(PaymentStrategy):
"""支付宝支付"""
def __init__(self, account: str):
self.account = account
def pay(self, amount: float) -> bool:
print(f"正在调用支付宝SDK...")
print(f"账号 {self.account} 支付宝支付 {amount} 元")
return True
def get_name(self) -> str:
return "支付宝"
class WechatPayStrategy(PaymentStrategy):
"""微信支付"""
def __init__(self, openid: str):
self.openid = openid
def pay(self, amount: float) -> bool:
print(f"正在调用微信支付SDK...")
print(f"用户 {self.openid} 微信支付 {amount} 元")
return True
def get_name(self) -> str:
return "微信支付"
class CreditCardStrategy(PaymentStrategy):
"""信用卡支付"""
def __init__(self, card_number: str, cvv: str):
self.card_number = card_number
self.cvv = cvv
def pay(self, amount: float) -> bool:
masked_card = f"{self.card_number[:4]}****{self.card_number[-4:]}"
print(f"正在调用银行接口...")
print(f"信用卡 {masked_card} 支付 {amount} 元")
return True
def get_name(self) -> str:
return "信用卡"
# 3. 上下文类
class PaymentContext:
"""支付上下文"""
def __init__(self, strategy: Optional[PaymentStrategy] = None):
self._strategy = strategy
def set_strategy(self, strategy: PaymentStrategy):
"""切换支付策略"""
self._strategy = strategy
print(f"已切换到【{strategy.get_name()}】")
def execute_payment(self, amount: float) -> bool:
"""执行支付"""
if self._strategy is None:
raise ValueError("请先选择支付方式!")
print(f"\n{'='*40}")
print(f"开始支付,金额:{amount} 元")
print(f"支付方式:{self._strategy.get_name()}")
print(f"{'='*40}")
result = self._strategy.pay(amount)
print("支付成功!" if result else "支付失败!")
return result
用起来是这样的:
python
payment = PaymentContext()
# 用支付宝
alipay = AlipayStrategy(account="user@example.com")
payment.set_strategy(alipay)
payment.execute_payment(99.9)
# 换微信
wechat = WechatPayStrategy(openid="wx_123456")
payment.set_strategy(wechat)
payment.execute_payment(199.0)
这种写法中规中矩,不过说实话,在 Python 里显得有点啰嗦。
3.2 函数作为策略------更 Pythonic 的写法
Python 里函数是一等公民,直接拿函数当策略用,代码能短不少:
python
from typing import Callable, Dict
from functools import partial
PaymentFunc = Callable[[float], bool]
def alipay_payment(amount: float, account: str) -> bool:
"""支付宝支付"""
print(f"支付宝账号 {account} 支付 {amount} 元")
return True
def wechat_payment(amount: float, openid: str) -> bool:
"""微信支付"""
print(f"微信用户 {openid} 支付 {amount} 元")
return True
def credit_card_payment(amount: float, card_number: str) -> bool:
"""信用卡支付"""
print(f"信用卡 {card_number[:4]}**** 支付 {amount} 元")
return True
class PaymentFactory:
"""用工厂模式配合一下"""
@staticmethod
def create(payment_type: str, **kwargs) -> PaymentFunc:
strategies: Dict[str, PaymentFunc] = {
"alipay": partial(alipay_payment, account=kwargs.get("account", "")),
"wechat": partial(wechat_payment, openid=kwargs.get("openid", "")),
"credit_card": partial(credit_card_payment, card_number=kwargs.get("card_number", "")),
}
if payment_type not in strategies:
raise ValueError(f"不支持的支付方式: {payment_type}")
return strategies[payment_type]
使用:
python
pay_func = PaymentFactory.create("alipay", account="test@qq.com")
pay_func(100.0)
pay_func = PaymentFactory.create("wechat", openid="wx_789")
pay_func(200.0)
这样写简洁多了,而且用 partial 预绑定参数也很方便。
3.3 字典映射------最省事的方式
如果策略逻辑不复杂,直接用字典映射就完事了:
python
from dataclasses import dataclass
from typing import Callable, Dict
@dataclass
class PaymentRequest:
amount: float
method: str
extra_info: dict
class SimplePaymentProcessor:
"""简化版支付处理器"""
def __init__(self):
self._strategies: Dict[str, Callable] = {
"alipay": self._pay_alipay,
"wechat": self._pay_wechat,
"credit_card": self._pay_credit_card,
}
def _pay_alipay(self, request: PaymentRequest) -> dict:
return {
"success": True,
"message": f"支付宝支付 {request.amount} 元成功",
"transaction_id": "ALI_123456"
}
def _pay_wechat(self, request: PaymentRequest) -> dict:
return {
"success": True,
"message": f"微信支付 {request.amount} 元成功",
"transaction_id": "WX_789012"
}
def _pay_credit_card(self, request: PaymentRequest) -> dict:
return {
"success": True,
"message": f"信用卡支付 {request.amount} 元成功",
"transaction_id": "CC_345678"
}
def register_strategy(self, name: str, handler: Callable):
"""运行时加新策略"""
self._strategies[name] = handler
print(f"已注册新支付方式: {name}")
def process(self, request: PaymentRequest) -> dict:
strategy = self._strategies.get(request.method)
if not strategy:
return {"success": False, "message": f"不支持的支付方式: {request.method}"}
return strategy(request)
这种方式最大的好处是可以运行时动态加策略:
python
processor = SimplePaymentProcessor()
# 动态加个花呗
def pay_by_huabei(request: PaymentRequest) -> dict:
return {
"success": True,
"message": f"花呗支付 {request.amount} 元成功",
"transaction_id": "HB_999"
}
processor.register_strategy("huabei", pay_by_huabei)
实际项目里这种写法用得挺多的。
四、Python vs Java策略模式对比
既然说到这了,顺便对比下 Python 和 Java 实现策略模式的区别。
4.1 代码量差距
Java 版本(省略了一些样板代码,实际更长):
java
// 接口
public interface PaymentStrategy {
boolean pay(double amount);
String getName();
}
// 实现类(每个通常要单独一个文件)
public class AlipayStrategy implements PaymentStrategy {
private String account;
public AlipayStrategy(String account) {
this.account = account;
}
@Override
public boolean pay(double amount) {
System.out.println("支付宝支付 " + amount + " 元");
return true;
}
@Override
public String getName() {
return "支付宝";
}
}
// 上下文
public class PaymentContext {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public boolean executePayment(double amount) {
if (strategy == null) {
throw new IllegalStateException("请先选择支付方式");
}
return strategy.pay(amount);
}
}
Python 版本(可以全放一个文件):
python
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount: float) -> bool: pass
class AlipayStrategy(PaymentStrategy):
def __init__(self, account: str):
self.account = account
def pay(self, amount: float) -> bool:
print(f"支付宝支付 {amount} 元")
return True
# 或者直接用函数,更短
def simple_alipay(amount: float) -> bool:
print(f"支付宝支付 {amount} 元")
return True
差距还是挺明显的。
4.2 主要区别
| 特性 | Python | Java |
|---|---|---|
| 接口定义 | ABC/Protocol/直接用函数 | 必须用 interface |
| 函数当参数 | 天然支持 | 需要接口包装 |
| 动态加策略 | 字典存函数就行 | 相对麻烦 |
| 代码量 | 少 | 多 |
4.3 Python 的一些骚操作
Python 还能用 Protocol 做结构化子类型,这个挺好用的:
python
from typing import Protocol, runtime_checkable
@runtime_checkable
class PaymentProtocol(Protocol):
def pay(self, amount: float) -> bool: ...
# 任何有 pay 方法的类都自动符合这个协议
class SomeThirdPartyPayment:
def pay(self, amount: float) -> bool:
print(f"第三方支付 {amount} 元")
return True
# 不用显式继承
payment: PaymentProtocol = SomeThirdPartyPayment()
print(isinstance(payment, PaymentProtocol)) # True
还有用装饰器注册策略的:
python
class StrategyRegistry:
_strategies = {}
@classmethod
def register(cls, name: str):
def decorator(func):
cls._strategies[name] = func
return func
return decorator
@classmethod
def get(cls, name: str):
return cls._strategies.get(name)
@StrategyRegistry.register("alipay")
def alipay_handler(amount: float) -> bool:
print(f"支付宝: {amount}")
return True
# 使用
handler = StrategyRegistry.get("alipay")
handler(100)
这种写法在框架源码里经常能看到。
五、FastAPI实战:构建支付服务
光说不练假把式,下面来个实际的例子:用 FastAPI 搭个支付服务。
5.1 项目结构
payment_service/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI入口
│ ├── models/
│ │ ├── __init__.py
│ │ └── schemas.py # Pydantic模型
│ ├── strategies/
│ │ ├── __init__.py
│ │ ├── base.py # 策略基类
│ │ ├── alipay.py
│ │ ├── wechat.py
│ │ └── credit_card.py
│ ├── services/
│ │ ├── __init__.py
│ │ └── payment_service.py
│ └── api/
│ ├── __init__.py
│ └── payment.py
├── requirements.txt
└── README.md
5.2 数据模型
app/models/schemas.py
python
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
from enum import Enum
from datetime import datetime
class PaymentMethod(str, Enum):
ALIPAY = "alipay"
WECHAT = "wechat"
CREDIT_CARD = "credit_card"
APPLE_PAY = "apple_pay"
class PaymentRequest(BaseModel):
order_id: str = Field(..., description="订单ID", example="ORDER_20231201_001")
amount: float = Field(..., gt=0, description="支付金额", example=99.99)
method: PaymentMethod = Field(..., description="支付方式")
extra_params: Optional[Dict[str, Any]] = Field(default=None, description="额外参数")
class Config:
json_schema_extra = {
"example": {
"order_id": "ORDER_20231201_001",
"amount": 99.99,
"method": "alipay",
"extra_params": {"account": "user@example.com"}
}
}
class PaymentResponse(BaseModel):
success: bool
message: str
transaction_id: Optional[str] = None
order_id: str
amount: float
method: str
timestamp: datetime = Field(default_factory=datetime.now)
extra_data: Optional[Dict[str, Any]] = None
class PaymentMethodInfo(BaseModel):
code: str
name: str
description: str
enabled: bool
icon: Optional[str] = None
5.3 策略定义
app/strategies/base.py
python
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional
from dataclasses import dataclass
import uuid
from datetime import datetime
@dataclass
class PaymentResult:
success: bool
transaction_id: Optional[str]
message: str
extra_data: Optional[Dict[str, Any]] = None
class PaymentStrategy(ABC):
"""支付策略基类"""
@property
@abstractmethod
def name(self) -> str:
pass
@property
@abstractmethod
def code(self) -> str:
pass
@property
def description(self) -> str:
return f"使用{self.name}进行支付"
@abstractmethod
async def execute(
self,
order_id: str,
amount: float,
extra_params: Optional[Dict[str, Any]] = None
) -> PaymentResult:
pass
@abstractmethod
async def validate_params(
self,
extra_params: Optional[Dict[str, Any]]
) -> tuple[bool, str]:
pass
def generate_transaction_id(self) -> str:
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
unique_id = str(uuid.uuid4())[:8].upper()
return f"{self.code.upper()}_{timestamp}_{unique_id}"
app/strategies/alipay.py
python
import asyncio
from typing import Dict, Any, Optional
from .base import PaymentStrategy, PaymentResult
class AlipayStrategy(PaymentStrategy):
@property
def name(self) -> str:
return "支付宝"
@property
def code(self) -> str:
return "alipay"
@property
def description(self) -> str:
return "使用支付宝APP或支付宝账号进行支付"
async def validate_params(
self,
extra_params: Optional[Dict[str, Any]]
) -> tuple[bool, str]:
if not extra_params:
return False, "缺少支付宝账号信息"
account = extra_params.get("account")
if not account:
return False, "缺少支付宝账号"
if "@" not in account and not account.isdigit():
return False, "支付宝账号格式不对"
return True, ""
async def execute(
self,
order_id: str,
amount: float,
extra_params: Optional[Dict[str, Any]] = None
) -> PaymentResult:
is_valid, error_msg = await self.validate_params(extra_params)
if not is_valid:
return PaymentResult(success=False, transaction_id=None, message=error_msg)
account = extra_params.get("account")
print(f"[支付宝] 处理订单: {order_id}")
print(f"[支付宝] 账号: {account}, 金额: {amount}")
# 模拟调用SDK
await asyncio.sleep(0.5)
transaction_id = self.generate_transaction_id()
print(f"[支付宝] 支付成功, 交易号: {transaction_id}")
return PaymentResult(
success=True,
transaction_id=transaction_id,
message="支付宝支付成功",
extra_data={
"account": account,
"payment_time": datetime.now().isoformat(),
"alipay_trade_no": f"ALI{transaction_id}"
}
)
微信和信用卡的实现类似,就不贴了。
5.4 支付服务
app/services/payment_service.py
python
from typing import Dict, Optional, List
from app.strategies import (
PaymentStrategy,
PaymentResult,
AlipayStrategy,
WechatPayStrategy,
CreditCardStrategy,
)
from app.models.schemas import PaymentMethod, PaymentMethodInfo
class PaymentService:
"""支付服务------策略调度中心"""
def __init__(self):
self._strategies: Dict[str, PaymentStrategy] = {}
self._register_default_strategies()
def _register_default_strategies(self):
self.register_strategy(PaymentMethod.ALIPAY.value, AlipayStrategy())
self.register_strategy(PaymentMethod.WECHAT.value, WechatPayStrategy())
self.register_strategy(PaymentMethod.CREDIT_CARD.value, CreditCardStrategy())
def register_strategy(self, method_code: str, strategy: PaymentStrategy):
self._strategies[method_code] = strategy
print(f"已注册支付策略: {strategy.name} ({method_code})")
def unregister_strategy(self, method_code: str):
if method_code in self._strategies:
strategy = self._strategies.pop(method_code)
print(f"已注销支付策略: {strategy.name}")
def get_strategy(self, method_code: str) -> Optional[PaymentStrategy]:
return self._strategies.get(method_code)
def get_available_methods(self) -> List[PaymentMethodInfo]:
return [
PaymentMethodInfo(
code=code,
name=strategy.name,
description=strategy.description,
enabled=True
)
for code, strategy in self._strategies.items()
]
async def process_payment(
self,
order_id: str,
amount: float,
method: str,
extra_params: Optional[Dict] = None
) -> PaymentResult:
strategy = self.get_strategy(method)
if not strategy:
return PaymentResult(
success=False,
transaction_id=None,
message=f"不支持的支付方式: {method}",
extra_data={"available_methods": list(self._strategies.keys())}
)
print(f"\n{'='*50}")
print(f"订单: {order_id} | 金额: {amount} | 方式: {strategy.name}")
print(f"{'='*50}")
try:
return await strategy.execute(order_id, amount, extra_params)
except Exception as e:
return PaymentResult(
success=False,
transaction_id=None,
message=f"支付异常: {str(e)}"
)
# 全局单例
payment_service = PaymentService()
5.5 API 路由
app/api/payment.py
python
from fastapi import APIRouter
from typing import List
from app.models.schemas import PaymentRequest, PaymentResponse, PaymentMethodInfo
from app.services.payment_service import payment_service
from datetime import datetime
router = APIRouter(prefix="/api/payment", tags=["支付接口"])
@router.get("/methods", response_model=List[PaymentMethodInfo], summary="获取支付方式")
async def get_payment_methods():
return payment_service.get_available_methods()
@router.post("/pay", response_model=PaymentResponse, summary="发起支付")
async def create_payment(request: PaymentRequest):
result = await payment_service.process_payment(
order_id=request.order_id,
amount=request.amount,
method=request.method.value,
extra_params=request.extra_params
)
return PaymentResponse(
success=result.success,
message=result.message,
transaction_id=result.transaction_id,
order_id=request.order_id,
amount=request.amount,
method=request.method.value,
timestamp=datetime.now(),
extra_data=result.extra_data
)
app/main.py
python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api import payment
app = FastAPI(
title="支付服务 API",
description="使用策略模式实现的支付服务",
version="1.0.0"
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(payment.router)
@app.get("/")
async def root():
return {"service": "Payment Service", "status": "running", "docs": "/docs"}
if __name__ == "__main__":
import uvicorn
uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
5.6 加新支付方式
这是策略模式最爽的地方------加新支付方式只需要:
- 写个新策略类:
python
# app/strategies/apple_pay.py
class ApplePayStrategy(PaymentStrategy):
@property
def name(self) -> str:
return "Apple Pay"
@property
def code(self) -> str:
return "apple_pay"
async def validate_params(self, extra_params):
if not extra_params or not extra_params.get("payment_token"):
return False, "缺少payment_token"
return True, ""
async def execute(self, order_id, amount, extra_params=None):
is_valid, error = await self.validate_params(extra_params)
if not is_valid:
return PaymentResult(False, None, error)
await asyncio.sleep(0.3)
transaction_id = self.generate_transaction_id()
return PaymentResult(
success=True,
transaction_id=transaction_id,
message="Apple Pay支付成功"
)
- 注册一下:
python
payment_service.register_strategy("apple_pay", ApplePayStrategy())
完了,原来的代码一行不用动。
六、策略模式使用场景与注意事项
6.1 什么时候用
策略模式比较适合这些场景:
多种算法可以互换的时候,比如不同的排序算法、不同的验证方式、不同的支付渠道。
想干掉一堆 if-else 的时候,尤其是那种根据类型执行不同逻辑的代码,用策略模式重构后清爽很多。
需要经常加新算法的时候,比如电商的促销策略,今天满减明天折扣后天发券,用策略模式加起来方便。
6.2 优缺点
优点很明显:符合开闭原则,加新策略不用改老代码;每个策略单独测试,出问题好排查;运行时想换就换,灵活。
缺点也有:策略类会变多,项目里一堆小文件看着有点烦;客户端得知道有哪些策略可以选,多了一层认知成本。
6.3 注意事项
别过度设计。如果就两三种情况,以后也不太会加,老老实实写 if-else 可能更直观。策略模式是用来解决问题的,不是用来炫技的。
接口要统一 。所有策略的方法签名得一致,不然上下文没法统一调用。如果不同策略需要不同参数,可以用 **kwargs 或者专门的参数对象来处理。
考虑默认策略。万一用户没选或者选了个不存在的,最好有个兜底方案。
6.4 和其他模式的区别
策略模式容易和状态模式、模板方法混淆:
- 策略模式:算法可以互换,客户端主动选择
- 状态模式:行为随状态变化而变化,状态自己管理切换
- 模板方法:算法骨架固定,子类实现具体步骤
简单说,策略模式关注"怎么选",状态模式关注"什么时候变",模板方法关注"整体流程"。
七、总结
写了这么多,最后简单总结一下。
策略模式的核心就是把不同的算法封装起来,让它们可以互相替换。Python 实现起来比 Java 省事多了,用抽象基类、函数、字典都行,看场景选就好。
实际开发中,策略模式在支付处理、数据验证、文件导出这类有多种实现方式的场景特别好用。最大的好处就是加新逻辑不用改老代码,维护起来舒服。
不过也别啥都往策略模式上套,逻辑简单的 if-else 就够了,没必要为了设计模式而设计模式。根据实际需求来,能解决问题的方案才是好方案。
热门专栏推荐
- Agent小册
- 服务器部署
- Java基础合集
- Python基础合集
- Go基础合集
- 大数据合集
- 前端小册
- 数据库合集
- Redis 合集
- Spring 全家桶
- 微服务全家桶
- 数据结构与算法合集
- 设计模式小册
- 消息队列合集
等等等还有许多优秀的合集在主页等着大家的光顾,感谢大家的支持
文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论
希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力