strategy-pattern-python
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStrategy Pattern (Python Backend)
策略模式(Python后端)
Why: Strategy lets you define a family of algorithms, put each in a separate class, and make them interchangeable so the context stays stable while behavior is swapped at runtime (Refactoring.Guru).
Hard constraints: Context must depend only on a strategy protocol/ABC, not concrete implementations. Use composition (context holds a strategy reference); avoid inheritance for variant behavior. Keep each strategy in its own class/module when it has real logic.
原因: 策略模式允许你定义一族算法,将每个算法封装到独立的类中,并使它们可互换,这样在运行时切换行为时,上下文能保持稳定(参考Refactoring.Guru)。
硬约束: 上下文必须仅依赖于策略protocol/ABC,而非具体实现。使用组合方式(上下文持有策略引用);避免通过继承实现可变行为。当策略包含实际逻辑时,将每个策略放在独立的类/模块中。
When to use
适用场景
- Different variants of the same algorithm (e.g. payment methods, route builders, serializers) and you want to switch at runtime.
- A class is bloated with conditionals (e.g. ); extract each branch into a strategy.
if method == "card": ... elif method == "paypal": ... - You need to isolate algorithm details from the rest of the backend logic (Open/Closed: add strategies without changing context).
- 同一算法存在不同变体(如支付方式、路线构建器、序列化器),且你需要在运行时切换。
- 某个类因大量条件判断而臃肿(如);可将每个分支提取为独立策略。
if method == "card": ... elif method == "paypal": ... - 你需要将算法细节与后端其余逻辑隔离(开闭原则:添加新策略无需修改上下文)。
Structure
结构
| Role | Responsibility |
|---|---|
| Context | Holds a reference to one strategy; delegates the varying work to it; exposes a setter (or constructor) so clients can inject/replace the strategy. |
| Strategy (protocol/ABC) | Common contract for all strategies (e.g. single method like |
| Concrete strategies | Implement the protocol/ABC; each encapsulates one variant of the algorithm. |
| Client | Chooses a concrete strategy and passes it to the context (e.g. from request params, config, or factory). |
Context does not know concrete strategy types—only the interface.
| 角色 | 职责 |
|---|---|
| 上下文(Context) | 持有一个策略的引用;将可变工作委托给策略;暴露setter方法(或构造函数),以便客户端注入/替换策略。 |
| 策略(Strategy,protocol/ABC) | 所有策略的通用契约(例如单个方法 |
| 具体策略(Concrete strategies) | 实现protocol/ABC;每个策略封装算法的一种变体。 |
| 客户端(Client) | 选择具体策略并将其传递给上下文(例如从请求参数、配置或工厂中获取)。 |
上下文不知道具体策略的类型——仅知晓其接口。
Code contrast
代码对比
❌ ANTI-PATTERN: Bloated handler with conditionals
❌ 反模式:臃肿的条件判断处理器
python
undefinedpython
undefinedOne big class; every new payment method forces edits here.
一个庞大的类;每新增一种支付方式都必须修改此处。
class PaymentService:
def process_payment(self, amount: float, method: str, details: dict) -> dict:
if method == "stripe":
return self._stripe_charge(amount, details)
if method == "paypal":
return self._paypal_charge(amount, details)
if method == "bank":
return self._bank_transfer(amount, details)
raise ValueError("Unknown method")
def _stripe_charge(self, amount: float, details: dict) -> dict: ...
def _paypal_charge(self, amount: float, details: dict) -> dict: ...
def _bank_transfer(self, amount: float, details: dict) -> dict: ...
Problems: context grows with every variant; touching one method risks breaking others; hard to test in isolation; violates Open/Closed.class PaymentService:
def process_payment(self, amount: float, method: str, details: dict) -> dict:
if method == "stripe":
return self._stripe_charge(amount, details)
if method == "paypal":
return self._paypal_charge(amount, details)
if method == "bank":
return self._bank_transfer(amount, details)
raise ValueError("Unknown method")
def _stripe_charge(self, amount: float, details: dict) -> dict: ...
def _paypal_charge(self, amount: float, details: dict) -> dict: ...
def _bank_transfer(self, amount: float, details: dict) -> dict: ...
问题:上下文会随每个变体不断膨胀;修改一个方法可能影响其他方法;难以独立测试;违反开闭原则。✅ TOP-CODER PATTERN: Strategy protocol + concrete strategies + context
✅ 最优实践:策略protocol + 具体策略 + 上下文
Strategy protocol (contract):
python
undefined策略protocol(契约):
python
undefinedstrategies/payment_strategy.py
strategies/payment_strategy.py
from typing import Protocol, runtime_checkable
@runtime_checkable
class PaymentStrategy(Protocol):
def execute(self, amount: float, details: dict) -> dict:
"""Return dict with at least 'id' key."""
...
**Concrete strategies** (one variant per class):
```pythonfrom typing import Protocol, runtime_checkable
@runtime_checkable
class PaymentStrategy(Protocol):
def execute(self, amount: float, details: dict) -> dict:
"""返回至少包含'id'键的字典。"""
...
**具体策略(每个变体对应一个类):**
```pythonstrategies/stripe_strategy.py
strategies/stripe_strategy.py
class StripeStrategy:
def execute(self, amount: float, details: dict) -> dict:
payment_intent = stripe.PaymentIntent.create(amount=amount, **details)
return {"id": payment_intent.id}
class StripeStrategy:
def execute(self, amount: float, details: dict) -> dict:
payment_intent = stripe.PaymentIntent.create(amount=amount, **details)
return {"id": payment_intent.id}
strategies/paypal_strategy.py
strategies/paypal_strategy.py
class PaypalStrategy:
def execute(self, amount: float, details: dict) -> dict:
order = paypal_client.orders.create(amount=amount, **details)
return {"id": order.id}
**Context** (depends only on the protocol):
```pythonclass PaypalStrategy:
def execute(self, amount: float, details: dict) -> dict:
order = paypal_client.orders.create(amount=amount, **details)
return {"id": order.id}
**上下文(仅依赖于protocol):**
```pythonservices/payment_context.py
services/payment_context.py
class PaymentContext:
def init(self, strategy: PaymentStrategy) -> None:
self._strategy = strategy
def set_strategy(self, strategy: PaymentStrategy) -> None:
self._strategy = strategy
def process_payment(self, amount: float, details: dict) -> dict:
return self._strategy.execute(amount, details)
**Client** (e.g. FastAPI route) selects strategy and calls context:
```pythonclass PaymentContext:
def init(self, strategy: PaymentStrategy) -> None:
self._strategy = strategy
def set_strategy(self, strategy: PaymentStrategy) -> None:
self._strategy = strategy
def process_payment(self, amount: float, details: dict) -> dict:
return self._strategy.execute(amount, details)
**客户端(例如FastAPI路由)选择策略并调用上下文:**
```pythonroutes/payments.py
routes/payments.py
strategies: dict[str, PaymentStrategy] = {
"stripe": StripeStrategy(),
"paypal": PaypalStrategy(),
}
context = PaymentContext(strategies["stripe"])
@router.post("/pay")
def pay(body: PayBody) -> dict:
strategy = strategies.get(body.method, strategies["stripe"])
context.set_strategy(strategy)
return context.process_payment(body.amount, body.details)
Benefits: add new payment methods by adding a new strategy class and registering it; context and other strategies stay unchanged; each strategy is easy to unit test.
---strategies: dict[str, PaymentStrategy] = {
"stripe": StripeStrategy(),
"paypal": PaypalStrategy(),
}
context = PaymentContext(strategies["stripe"])
@router.post("/pay")
def pay(body: PayBody) -> dict:
strategy = strategies.get(body.method, strategies["stripe"])
context.set_strategy(strategy)
return context.process_payment(body.amount, body.details)
优势:新增支付方式只需添加新的策略类并注册;上下文和其他策略无需修改;每个策略易于进行单元测试。
---Python backend notes
Python后端注意事项
- Protocol vs ABC: Prefer for structural subtyping (no inheritance required). Use
typing.Protocolwhen you need a shared base with default or mixin behavior.abc.ABC - Async: If strategies do I/O, use and
async def execute(...); context awaits and returns. Keep sync and async protocol/contexts separate if you mix both.AsyncPaymentStrategy - DI: Inject the strategy (or a strategy factory) into the context so tests can pass mocks or fakes; works well with FastAPI dependencies or Django injection.
- Modules: One module per concrete strategy when logic is non-trivial (e.g. ); keep the protocol in a shared module (e.g.
strategies/stripe_strategy.pyorstrategies/base.py).strategies/payment_strategy.py - No overkill: If you only have one or two fixed algorithms and they rarely change, a simple conditional or single implementation may be enough; avoid extra classes for the sake of it.
- Protocol vs ABC: 优先使用进行结构子类型化(无需继承)。当你需要带有默认行为或混入(mixin)的共享基类时,使用
typing.Protocol。abc.ABC - 异步处理: 如果策略涉及I/O操作,使用和
async def execute(...);上下文需使用await并返回结果。如果同时存在同步和异步场景,需将同步和异步的protocol/上下文分开。AsyncPaymentStrategy - 依赖注入(DI): 将策略(或策略工厂)注入到上下文中,以便测试时能传递模拟(mock)或伪造(fake)对象;这与FastAPI依赖项或Django注入能很好地配合使用。
- 模块组织: 当策略逻辑非 trivial 时,每个具体策略对应一个模块(例如);将protocol放在共享模块中(例如
strategies/stripe_strategy.py或strategies/base.py)。strategies/payment_strategy.py - 避免过度设计: 如果你只有一两个固定算法且很少变更,简单的条件判断或单一实现可能就足够;不要为了模式而额外增加类。
Reference
参考资料
- Strategy pattern — Refactoring.Guru: intent, problem/solution, structure, applicability, pros/cons, relations with State/Command/Template Method.
- 策略模式 — Refactoring.Guru:意图、问题/解决方案、结构、适用性、优缺点,以及与状态/命令/模板方法模式的关系。