21、设计模式:策略模式:算法族封装,消除if-else,实战案例:促销活动折扣计算

策略模式,说白了就是「把一堆长得像的算法,各自装进独立的盒子里,然后让它们可以互相替换」。我最早接触这个模式,是在一个电商项目里。那时候促销活动特别多,什么满减、打折、秒杀、会员价……代码里全是 if-else,改一个逻辑要翻半天,还经常改出 bug。后来用了策略模式,整个世界清净了。

为什么需要策略模式?

你想想看,如果有一天产品经理说:「我们要加一个『买三送一』的活动。」你打开代码,找到那个几百行的 if-else 链,小心翼翼地加上一个分支。测试通过了,上线了。第二天,产品又说:「把『买三送一』改成『买二送一』。」你又去改那个分支。改完发现,满减的逻辑也被你碰坏了。

为什么会这样?因为多个算法混在一起,耦合太紧。策略模式就是来解决这个问题的——把每个算法独立出来,互不干扰。

核心思想:定义一组算法,把它们各自封装起来,并且使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。

策略模式的结构

策略模式有三个角色:

  • 策略接口(Strategy):定义所有策略必须实现的方法。
  • 具体策略(ConcreteStrategy):实现策略接口,封装具体的算法。
  • 上下文(Context):持有一个策略对象的引用,负责调用策略。

我习惯把上下文叫做「调度器」。它不关心具体怎么算,只负责把任务派给对应的策略。

策略模式结构图 Context(上下文) - strategy: Strategy Strategy(策略接口) + execute(data) ConcreteStrategyA 满减策略 ConcreteStrategyB 打折策略 ConcreteStrategyC 秒杀策略

实战案例:促销活动折扣计算

我们直接上代码。假设一个电商系统,需要支持多种折扣方式:不打折、打八折、满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。我曾经在一个项目里看到,同一个折扣逻辑在三个文件里各写了一遍,结果改漏了一个,线上出了资损……

用策略模式之后,新增活动只需要:

  1. 新建一个策略类
  2. 在配置里注册
  3. 完事

不需要改任何已有的代码。这就是「开闭原则」——对扩展开放,对修改关闭。

策略模式的适用场景

场景 说明
多种算法/行为 比如排序算法、支付方式、验证规则
需要动态切换 运行时根据条件选择不同策略
避免大量条件判断 if-else 或 switch 分支太多时
算法族需要独立变化 每个算法有自己的复杂度,互不影响
注意:策略模式不是银弹。如果只有两三种策略,而且几乎不变,用 if-else 反而更简单。不要为了用模式而用模式。

避坑指南

我曾经犯过一个错误:把所有策略类都写在一个文件里,结果文件越来越长,反而不好维护。后来我养成了一个习惯——每个策略类单独一个文件,按功能模块分目录。比如 discounts/no_discount.pydiscounts/percent.py。这样找起来快,改起来也放心。

另外,策略对象如果是无状态的(比如不打折、打折比例固定),可以做成单例,避免重复创建。我一般用类方法或者模块级别的实例来共享。

小结

策略模式的核心,就是把「做什么」和「怎么做」分开。上下文只关心「我要做这件事」,具体策略负责「怎么做」。这样,算法族可以独立演化,互不干扰。

嗯,其实设计模式没那么玄乎。你想想看,它就是把我们平时写代码时「好的习惯」给总结出来了。策略模式说白了就是「别把所有逻辑堆在一起,拆开,各管各的」。就这么简单。


公众号:蓝海资料掘金营,微信deep3321