21、设计模式:策略模式:算法族封装,消除if-else,实战案例:促销活动折扣计算
策略模式,说白了就是「把一堆长得像的算法,各自装进独立的盒子里,然后让它们可以互相替换」。我最早接触这个模式,是在一个电商项目里。那时候促销活动特别多,什么满减、打折、秒杀、会员价……代码里全是 if-else,改一个逻辑要翻半天,还经常改出 bug。后来用了策略模式,整个世界清净了。
为什么需要策略模式?
你想想看,如果有一天产品经理说:「我们要加一个『买三送一』的活动。」你打开代码,找到那个几百行的 if-else 链,小心翼翼地加上一个分支。测试通过了,上线了。第二天,产品又说:「把『买三送一』改成『买二送一』。」你又去改那个分支。改完发现,满减的逻辑也被你碰坏了。
为什么会这样?因为多个算法混在一起,耦合太紧。策略模式就是来解决这个问题的——把每个算法独立出来,互不干扰。
策略模式的结构
策略模式有三个角色:
- 策略接口(Strategy):定义所有策略必须实现的方法。
- 具体策略(ConcreteStrategy):实现策略接口,封装具体的算法。
- 上下文(Context):持有一个策略对象的引用,负责调用策略。
我习惯把上下文叫做「调度器」。它不关心具体怎么算,只负责把任务派给对应的策略。
实战案例:促销活动折扣计算
我们直接上代码。假设一个电商系统,需要支持多种折扣方式:不打折、打八折、满100减20、满200减50。
先定义策略接口:
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
"""折扣策略接口"""
@abstractmethod
def apply_discount(self, order_amount: float) -> float:
pass
然后实现各个具体策略:
class NoDiscount(DiscountStrategy):
"""不打折"""
def apply_discount(self, order_amount: float) -> float:
return order_amount
class PercentDiscount(DiscountStrategy):
"""打折,比如八折"""
def __init__(self, percent: float):
self.percent = percent
def apply_discount(self, order_amount: float) -> float:
return order_amount * (self.percent / 100)
class FullReductionDiscount(DiscountStrategy):
"""满减,比如满100减20"""
def __init__(self, threshold: float, reduction: float):
self.threshold = threshold
self.reduction = reduction
def apply_discount(self, order_amount: float) -> float:
if order_amount >= self.threshold:
return order_amount - self.reduction
return order_amount
接下来是上下文,也就是我们的订单计算器:
class OrderCalculator:
"""订单计算器,持有策略对象"""
def __init__(self, strategy: DiscountStrategy):
self._strategy = strategy
def set_strategy(self, strategy: DiscountStrategy):
"""运行时切换策略"""
self._strategy = strategy
def calculate(self, order_amount: float) -> float:
return self._strategy.apply_discount(order_amount)
使用起来非常清爽:
# 客户下单,金额200元
order = OrderCalculator(NoDiscount())
print(order.calculate(200)) # 200.0
# 切换为八折
order.set_strategy(PercentDiscount(80))
print(order.calculate(200)) # 160.0
# 切换为满100减20
order.set_strategy(FullReductionDiscount(100, 20))
print(order.calculate(200)) # 180.0
消除 if-else 的魔力
对比一下传统写法:
def calculate_discount(order_amount, discount_type):
if discount_type == 'none':
return order_amount
elif discount_type == 'percent_80':
return order_amount * 0.8
elif discount_type == 'full_100_minus_20':
return order_amount - 20 if order_amount >= 100 else order_amount
elif discount_type == 'full_200_minus_50':
return order_amount - 50 if order_amount >= 200 else order_amount
else:
raise ValueError('Unknown discount type')
这种代码,每加一种活动,就要改这个函数。而且多个地方用到折扣计算时,你得复制粘贴同样的 if-else。我曾经在一个项目里看到,同一个折扣逻辑在三个文件里各写了一遍,结果改漏了一个,线上出了资损……
用策略模式之后,新增活动只需要:
- 新建一个策略类
- 在配置里注册
- 完事
不需要改任何已有的代码。这就是「开闭原则」——对扩展开放,对修改关闭。
策略模式的适用场景
| 场景 | 说明 |
|---|---|
| 多种算法/行为 | 比如排序算法、支付方式、验证规则 |
| 需要动态切换 | 运行时根据条件选择不同策略 |
| 避免大量条件判断 | if-else 或 switch 分支太多时 |
| 算法族需要独立变化 | 每个算法有自己的复杂度,互不影响 |
避坑指南
我曾经犯过一个错误:把所有策略类都写在一个文件里,结果文件越来越长,反而不好维护。后来我养成了一个习惯——每个策略类单独一个文件,按功能模块分目录。比如 discounts/no_discount.py、discounts/percent.py。这样找起来快,改起来也放心。
另外,策略对象如果是无状态的(比如不打折、打折比例固定),可以做成单例,避免重复创建。我一般用类方法或者模块级别的实例来共享。
小结
策略模式的核心,就是把「做什么」和「怎么做」分开。上下文只关心「我要做这件事」,具体策略负责「怎么做」。这样,算法族可以独立演化,互不干扰。
嗯,其实设计模式没那么玄乎。你想想看,它就是把我们平时写代码时「好的习惯」给总结出来了。策略模式说白了就是「别把所有逻辑堆在一起,拆开,各管各的」。就这么简单。