python设计模式–策略模式(Strategy Pattern)

Python 设计模式——策略模式(Strategy Pattern)详解

本文面向零基础新手,从概念到“算法可替换”,从支付方式到排序比较器,全方位讲解策略模式。


一、什么是策略模式?

1.1 通俗理解

策略模式是一种行为型设计模式,核心思想是:

把“做同一件事的多种做法”(多种算法、多种策略)抽象成统一接口,每种做法是一个独立的“策略类”;使用方(上下文)不直接写死某一种做法,而是持有一个“策略”对象,把实际工作委托给策略。这样要换一种做法时,只需换一个策略对象,不必改使用方代码,也避免了满屏的 if-else 分支。

可以这样类比:

  • 不用策略:计算从 A 到 B 的路线,代码里写 if 选的是开车 then 按开车算,elif 选的是步行 then 按步行算,elif 选的是公交 then ...,每加一种出行方式就要改这段逻辑。
  • 用策略:定义“路线策略”接口(计算路线),有“开车策略”“步行策略”“公交策略”三个实现;使用方只持有一个“当前策略”,算路线时调用 strategy.calculate(A, B)。要换出行方式就 context.set_strategy(公交策略),使用方代码不变。算法可插拔、可替换。

所以,策略模式解决的是:同一类问题有多种解法,希望把每种解法封装成可替换的“策略”,让使用方依赖抽象策略而非具体实现,便于扩展和切换。

1.2 为什么需要策略模式?

场景一:多种算法可互换

例如支付:支付宝、微信、银行卡,都是“支付”这一件事的不同实现;下单时根据用户选择用哪一种,若写 if 支付方式==支付宝 then … elif 微信 then …,代码臃肿且难加新方式。用策略:每种支付方式是一个策略类,下单时注入对应策略,加新支付方式只需加新策略类

场景二:避免复杂分支

同一操作在不同条件下要不同处理(如不同用户等级用不同折扣),若全塞进一个函数里会充满 if-else。用策略:每种“折扣算法”一个策略,根据用户等级选一个策略注入,逻辑清晰、易测试

场景三:运行时切换行为

例如编辑器“保存”可以是“保存到本地”“保存到云”;用户可随时切换,使用方只调 strategy.save(doc),换策略就换行为,符合开闭原则

1.3 适用场景(什么时候用?)

场景 说明
同一问题多种算法/做法 支付、折扣、排序规则、路线计算等,希望可互换。
想避免大量 if-else 用多态代替“根据类型分支”。
算法要独立于使用方 使用方只依赖策略接口,具体算法可单独测试、替换。
运行时需切换行为 用户选择、配置决定用哪种策略。

简单记忆多种做法可互换、希望使用方不写死某一种、要便于扩展和切换时,用策略模式。


二、策略模式的结构(三个角色)

角色 说明
上下文(Context) 使用策略的一方;持有一个策略对象(接口类型),在需要执行“那件事”时调用 strategy.xxx(…),不关心具体是哪种实现。可提供 set_strategy(strategy) 以便切换策略。
策略(Strategy) 抽象接口,定义“做那件事”的方法(如 pay、calculate、execute)。
具体策略(ConcreteStrategy) 实现策略接口,每种实现代表一种算法/做法。

关系可以理解为:

  • 客户上下文在适当时机选择并注入一个具体策略(如根据用户选择、配置)。
  • 上下文在执行时只调 self._strategy.xxx(),不写 if 是哪种策略;换策略就换行为

三、示例一:支付方式(最经典)

需求:下单时要支付,支付方式有支付宝、微信、银行卡;根据用户选择使用不同方式,且便于以后加新方式。

3.1 策略接口 + 具体策略

from abc import ABC, abstractmethod

class PayStrategy(ABC):
    @abstractmethod
    def pay(self, amount: float) -> bool:
        pass

class AlipayStrategy(PayStrategy):
    def pay(self, amount: float) -> bool:
        print(f"  [支付宝] 支付 {amount} 元")
        return True

class WechatPayStrategy(PayStrategy):
    def pay(self, amount: float) -> bool:
        print(f"  [微信] 支付 {amount} 元")
        return True

class BankCardStrategy(PayStrategy):
    def pay(self, amount: float) -> bool:
        print(f"  [银行卡] 支付 {amount} 元")
        return True

3.2 上下文:订单(持有策略,支付时委托)

class Order:
    def __init__(self):
        self._pay_strategy = None

    def set_pay_strategy(self, strategy: PayStrategy):
        self._pay_strategy = strategy

    def do_pay(self, amount: float) -> bool:
        if not self._pay_strategy:
            print("  未选择支付方式")
            return False
        return self._pay_strategy.pay(amount)

3.3 使用

order = Order()
order.set_pay_strategy(AlipayStrategy())
order.do_pay(99.5)   # [支付宝] 支付 99.5 元

order.set_pay_strategy(WechatPayStrategy())
order.do_pay(50.0)   # [微信] 支付 50.0 元

要点:Order 不写 if 支付宝/微信/银行卡,只调 self._pay_strategy.pay(amount);换支付方式只换策略对象。


四、示例二:折扣策略(不同用户等级不同折扣)

需求:计算订单应付金额时,普通用户无折扣,VIP 九折,SVIP 八折。把“折扣算法”做成策略,根据用户等级选一个策略。

class DiscountStrategy(ABC):
    @abstractmethod
    def discount(self, amount: float) -> float:
        """返回折后金额"""
        pass

class NoDiscount(DiscountStrategy):
    def discount(self, amount: float) -> float:
        return amount

class VIPDiscount(DiscountStrategy):
    def discount(self, amount: float) -> float:
        return amount * 0.9

class SVIPDiscount(DiscountStrategy):
    def discount(self, amount: float) -> float:
        return amount * 0.8

class PriceCalculator:
    def __init__(self, strategy: DiscountStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: DiscountStrategy):
        self._strategy = strategy

    def final_price(self, amount: float) -> float:
        return self._strategy.discount(amount)

# 使用
calc = PriceCalculator(VIPDiscount())
print(calc.final_price(100))  # 90.0
calc.set_strategy(SVIPDiscount())
print(calc.final_price(100))  # 80.0

五、示例三:路线计算(不同出行方式)

需求:计算从 A 到 B 的路线(或耗时、距离);开车、步行、公交有不同的计算方式。每种方式一个策略。

class RouteStrategy(ABC):
    @abstractmethod
    def calculate(self, from_place: str, to_place: str) -> str:
        pass

class CarRoute(RouteStrategy):
    def calculate(self, from_place: str, to_place: str) -> str:
        return f"  驾车: {from_place} -> {to_place}, 约 20 分钟"

class WalkRoute(RouteStrategy):
    def calculate(self, from_place: str, to_place: str) -> str:
        return f"  步行: {from_place} -> {to_place}, 约 50 分钟"

class TransitRoute(RouteStrategy):
    def calculate(self, from_place: str, to_place: str) -> str:
        return f"  公交: {from_place} -> {to_place}, 约 35 分钟"

class Navigator:
    def __init__(self, strategy: RouteStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: RouteStrategy):
        self._strategy = strategy

    def route(self, from_place: str, to_place: str) -> str:
        return self._strategy.calculate(from_place, to_place)

# 使用
nav = Navigator(CarRoute())
print(nav.route("家", "公司"))
nav.set_strategy(TransitRoute())
print(nav.route("家", "公司"))

六、示例四:排序 / 比较策略(Python 的 key 与 cmp)

Python 内置的 sorted(lst, key=…) 就是一种“策略”思想:比较/排序规则通过 key 传入,可随时换 key 就换排序结果。若自己实现一个“可配置比较规则”的排序器,可以这样用策略封装:

class CompareStrategy(ABC):
    @abstractmethod
    def compare(self, a, b) -> int:
        """<0 表示 a<b, 0 表示 a==b, >0 表示 a>b"""
        pass

class IntAscStrategy(CompareStrategy):
    def compare(self, a, b) -> int:
        return a - b

class IntDescStrategy(CompareStrategy):
    def compare(self, a, b) -> int:
        return b - a

def sort_by_strategy(arr: list, strategy: CompareStrategy) -> list:
    return sorted(arr, key=lambda x: (strategy.compare(x, x), x))  # 简化:这里仅示意
# 更规范的做法是写一个 sort 函数内部用 strategy.compare 做两两比较

实际项目中通常直接用 sorted(lst, key=…) 或 functools.cmp_to_key,这里仅说明“比较规则可替换”即策略思想。


七、策略模式 vs 其他模式

模式 目的 与策略的区别
策略 算法/行为可替换,使用方委托给策略 策略由客户/上下文选择注入,一般不“自己切换”。
状态 行为随状态变,状态类可切换上下文的状态 状态会在处理中主动把上下文切到另一状态;策略通常不改变上下文。
工厂 根据类型创建对象 工厂负责“造哪个”;策略负责“用哪个算法”。可结合:用工厂造出策略再注入上下文。

简单记忆:策略 = 多种算法封装成可替换对象 + 使用方委托策略 + 换策略即换行为


八、常见问题与注意点

8.1 策略要不要在运行时切换?

可以。上下文提供 set_strategy,用户选择或配置变化时换一个策略即可;也可以一开始就固定一个策略,由构造或工厂决定。按需求选择。

8.2 策略需要访问上下文怎么办?

若策略执行时需要上下文的数据,可以把上下文(或部分数据)作为参数传给策略方法,如 strategy.pay(amount, order);或让策略持有上下文引用(注意避免循环引用)。通常传参更清晰。

8.3 何时不用策略模式?

若只有一两种固定做法、且几乎不会变,直接写在一个类里或简单 if 即可;策略模式适合多种做法、要互换或扩展的场景。


九、小结

  • 策略模式:把同一类问题的多种算法/做法封装成策略接口和多个具体策略上下文持有策略,把实际工作委托给策略,从而算法可替换、使用方不写死,便于扩展和运行时切换。
  • 三个角色:上下文(持有策略、委托调用)、策略(接口)、具体策略(各种实现)。
  • 典型用途:支付方式、折扣规则、路线计算、排序/比较规则、加密算法等“多种做法可互换”的场景。
  • 实现要点:上下文只依赖策略接口;换行为就 set_strategy(另一个策略);不加 if-else 按类型分支。

建议先写“支付策略”,再试“折扣策略”或“路线策略”,体会“注入谁就用谁、换策略就换行为”,这样对策略模式会掌握得比较扎实。

发表评论