strategy-pattern-python

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Strategy 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.
    if method == "card": ... elif method == "paypal": ...
    ); extract each branch into a strategy.
  • 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

结构

RoleResponsibility
ContextHolds 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
execute(data)
or
build_route(origin, dest)
).
Concrete strategiesImplement the protocol/ABC; each encapsulates one variant of the algorithm.
ClientChooses 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)所有策略的通用契约(例如单个方法
execute(data)
build_route(origin, dest)
)。
具体策略(Concrete strategies)实现protocol/ABC;每个策略封装算法的一种变体。
客户端(Client)选择具体策略并将其传递给上下文(例如从请求参数、配置或工厂中获取)。
上下文不知道具体策略的类型——仅知晓其接口。

Code contrast

代码对比

❌ ANTI-PATTERN: Bloated handler with conditionals

❌ 反模式:臃肿的条件判断处理器

python
undefined
python
undefined

One 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
undefined

strategies/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):

```python
from typing import Protocol, runtime_checkable
@runtime_checkable class PaymentStrategy(Protocol): def execute(self, amount: float, details: dict) -> dict: """返回至少包含'id'键的字典。""" ...

**具体策略(每个变体对应一个类):**

```python

strategies/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):

```python
class PaypalStrategy: def execute(self, amount: float, details: dict) -> dict: order = paypal_client.orders.create(amount=amount, **details) return {"id": order.id}

**上下文(仅依赖于protocol):**

```python

services/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:

```python
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)

**客户端(例如FastAPI路由)选择策略并调用上下文:**

```python

routes/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
    typing.Protocol
    for structural subtyping (no inheritance required). Use
    abc.ABC
    when you need a shared base with default or mixin behavior.
  • Async: If strategies do I/O, use
    async def execute(...)
    and
    AsyncPaymentStrategy
    ; context awaits and returns. Keep sync and async protocol/contexts separate if you mix both.
  • 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.
    strategies/stripe_strategy.py
    ); keep the protocol in a shared module (e.g.
    strategies/base.py
    or
    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: 优先使用
    typing.Protocol
    进行结构子类型化(无需继承)。当你需要带有默认行为或混入(mixin)的共享基类时,使用
    abc.ABC
  • 异步处理: 如果策略涉及I/O操作,使用
    async def execute(...)
    AsyncPaymentStrategy
    ;上下文需使用await并返回结果。如果同时存在同步和异步场景,需将同步和异步的protocol/上下文分开。
  • 依赖注入(DI): 将策略(或策略工厂)注入到上下文中,以便测试时能传递模拟(mock)或伪造(fake)对象;这与FastAPI依赖项或Django注入能很好地配合使用。
  • 模块组织: 当策略逻辑非 trivial 时,每个具体策略对应一个模块(例如
    strategies/stripe_strategy.py
    );将protocol放在共享模块中(例如
    strategies/base.py
    strategies/payment_strategy.py
    )。
  • 避免过度设计: 如果你只有一两个固定算法且很少变更,简单的条件判断或单一实现可能就足够;不要为了模式而额外增加类。

Reference

参考资料