23、Python - 策略模式

23、Python - 策略模式

你有没有写过这样的代码:一个方法里堆了几十个 if-elif-else,每加一种需求就要改一遍老代码,改完还生怕影响到其他分支?如果有,那么这篇文章就是为你写的。

策略模式是面试高频考点,也是企业项目中消灭 if-else 最常用的武器。本文将从真实痛点出发,用大白话、生活案例和可直接运行的 Python 代码,带你彻底搞懂策略模式。


文章目录

  • [23、Python - 策略模式](#23、Python - 策略模式)
    • [一、痛点场景:被 if-else 支配的恐惧](#一、痛点场景:被 if-else 支配的恐惧)
      • [1.1 真实项目中的烂代码](#1.1 真实项目中的烂代码)
      • [1.2 这种写法的三大痛点](#1.2 这种写法的三大痛点)
      • [1.3 生活中的类比](#1.3 生活中的类比)
    • 二、痛点的解决方案:把算法拆出来
    • 三、策略模式是什么
      • [3.1 官方定义](#3.1 官方定义)
      • [3.2 大白话解释](#3.2 大白话解释)
      • [3.3 三个核心角色](#3.3 三个核心角色)
      • [3.4 生活案例:出行方式选择](#3.4 生活案例:出行方式选择)
    • 四、为什么要用策略模式
      • [4.1 四大优点](#4.1 四大优点)
      • [4.2 两个缺点](#4.2 两个缺点)
      • [4.3 什么时候该用,什么时候不该用](#4.3 什么时候该用,什么时候不该用)
    • 五、策略模式是怎么演进过来的
      • [5.1 起源:建筑领域的模式思想](#5.1 起源:建筑领域的模式思想)
      • [5.2 引入软件工程:1987 年 OOPSLA 会议](#5.2 引入软件工程:1987 年 OOPSLA 会议)
      • [5.3 正式收录:1994 年 GoF 四人组](#5.3 正式收录:1994 年 GoF 四人组)
      • [5.4 广泛应用:2000 年代 Java 框架](#5.4 广泛应用:2000 年代 Java 框架)
      • [5.5 动态语言简化:2010 年代 Python 实现](#5.5 动态语言简化:2010 年代 Python 实现)
      • [5.6 云原生时代:2020 年代策略治理](#5.6 云原生时代:2020 年代策略治理)
    • 六、策略模式怎么用
      • [6.1 基础实现:电商折扣系统](#6.1 基础实现:电商折扣系统)
      • [6.2 进阶实现:策略注册中心(企业项目常用写法)](#6.2 进阶实现:策略注册中心(企业项目常用写法))
      • [6.3 Python 特色实现:用函数代替类](#6.3 Python 特色实现:用函数代替类)
      • [6.4 企业级实战:多渠道支付系统](#6.4 企业级实战:多渠道支付系统)
    • [七、策略模式 vs 竞品对比](#七、策略模式 vs 竞品对比)
      • [7.1 策略模式 vs 工厂模式](#7.1 策略模式 vs 工厂模式)
      • [7.2 策略模式 vs 状态模式](#7.2 策略模式 vs 状态模式)
      • [7.3 策略模式 vs 模板方法模式](#7.3 策略模式 vs 模板方法模式)
    • 八、策略模式常用场景
      • [8.1 电商折扣策略](#8.1 电商折扣策略)
      • [8.2 支付方式选择](#8.2 支付方式选择)
      • [8.3 导航路径算法](#8.3 导航路径算法)
      • [8.4 消息通知渠道](#8.4 消息通知渠道)
      • [8.5 排序算法切换](#8.5 排序算法切换)
      • [8.6 压缩格式选择](#8.6 压缩格式选择)
      • [8.7 日志记录策略](#8.7 日志记录策略)
    • 九、面试官高频面试题
    • 十、总结

一、痛点场景:被 if-else 支配的恐惧

1.1 真实项目中的烂代码

假设你在一家电商公司负责订单结算模块。最初只有一种支付方式------支付宝,代码很简单:

python 复制代码
def pay(order, method):
    if method == "alipay":
        print(f"使用支付宝支付 {order.amount} 元")
        # 调用支付宝SDK...
        return True

后来业务发展,接入了微信支付:

python 复制代码
def pay(order, method):
    if method == "alipay":
        print(f"使用支付宝支付 {order.amount} 元")
        return True
    elif method == "wechat":
        print(f"使用微信支付 {order.amount} 元")
        return True

再后来,银行卡、云闪付、Apple Pay、数字人民币......一个 pay 方法膨胀到了 300 多行:

python 复制代码
def pay(order, method, **kwargs):
    if method == "alipay":
        # 20行支付宝逻辑
        ...
    elif method == "wechat":
        # 25行微信逻辑
        ...
    elif method == "bank_card":
        # 40行银行卡逻辑,还要校验卡号、有效期、CVV
        ...
    elif method == "unionpay":
        # 15行云闪付逻辑
        ...
    elif method == "apple_pay":
        # 18行Apple Pay逻辑
        ...
    # ... 还有更多
    else:
        raise ValueError(f"不支持的支付方式: {method}")

1.2 这种写法的三大痛点

痛点一:违反开闭原则。 每新增一种支付方式,都必须修改 pay 函数的源码。改老代码就有风险,万一不小心删了一个空格导致语法错误,整个支付功能就挂了。

痛点二:代码臃肿难维护。 一个方法几百行,各种支付逻辑混在一起。想找微信支付的退款逻辑?在 300 行代码里慢慢翻吧。新人接手这个函数,光是读完就要半小时。

痛点三:无法动态切换。 如果用户在支付页面中途想换一种支付方式,你只能重新调用 pay 函数并传不同的 method 参数,无法在运行时灵活地替换算法。

1.3 生活中的类比

这就像你去餐厅吃饭,菜单上每加一道菜,厨师就要重新写一遍菜谱。本来菜谱应该是每道菜单独一页,结果全挤在一张纸上,改一道菜就要重写整张纸。

策略模式要做的,就是把每道菜的菜谱拆出来,单独放一页,想做哪道菜就翻哪一页。


二、痛点的解决方案:把算法拆出来

策略模式的核心思想只有一句话:把每个算法(策略)封装成独立的类,让它们可以互相替换。

回到支付的例子,策略模式会这样做:

  1. 定义一个支付策略接口,规定所有支付方式都必须实现 pay 方法
  2. 支付宝、微信、银行卡各自写成一个独立的策略类
  3. 定义一个上下文类(Context),它持有当前策略的引用,负责调用策略
  4. 客户端想换支付方式?直接给上下文换一个策略对象就行,不用改任何老代码

这样一来,新增支付方式只需要加一个新类,老代码一行都不用动。这就是开闭原则的完美体现。


三、策略模式是什么

3.1 官方定义

策略模式(Strategy Pattern)定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。该模式使得算法可以独立于使用它的客户端而变化。

该模式属于行为型模式,是 GoF 23 种设计模式之一。

3.2 大白话解释

用大白话说,策略模式就是:同样一件事,有多种做法,把每种做法单独打包,想用哪种就用哪种,随时可以换。

比如导航软件规划路线:

  • 做法A:时间最短(走高速)
  • 做法B:距离最短(走小路)
  • 做法C:红绿灯最少(走环线)

这三种做法就是三个策略,用户在导航界面点一下就能切换,导航软件不需要为每种路线写一套独立的导航流程。

3.3 三个核心角色

策略模式由三个角色组成:

角色 说明 生活类比
抽象策略(Strategy) 定义一个接口,规定所有策略必须实现的方法 菜谱的格式要求:必须有食材和步骤
具体策略(ConcreteStrategy) 实现策略接口,包含具体的算法逻辑 鱼香肉丝的菜谱、宫保鸡丁的菜谱
上下文(Context) 持有策略引用,负责调用策略执行算法 厨师,手里拿着当前要用的菜谱

3.4 生活案例:出行方式选择

一个人从北京去上海,可以选择:

  • 策略A:坐飞机(2小时,贵)
  • 策略B:坐高铁(5小时,中等)
  • 策略C:坐绿皮火车(20小时,便宜)

这三种出行方式就是三个策略。旅行者(上下文)可以根据预算和时间随时切换,不需要改变"从北京到上海"这个目标本身。


四、为什么要用策略模式

4.1 四大优点

优点一:算法可以自由切换。 客户端可以在运行时动态更换策略,不需要修改上下文代码。比如电商大促期间,折扣策略从"满减"切换到"第二件半价",只需要换一个策略对象。

优点二:避免使用多重条件判断。 没有策略模式时,你可能需要写 if type == A elif type == B else C。用了策略模式后,这些条件判断消失了,取而代之的是干净的策略类。

优点三:扩展性良好。 新增一种算法只需要新增一个策略类,完全符合开闭原则。老代码不需要修改,风险极低。

优点四:算法可以独立复用。 每个策略类都是独立的,可以在不同的上下文中复用。比如折扣策略既可以用在订单结算,也可以用在会员积分兑换。

4.2 两个缺点

缺点一:策略类会增多。 每多一种算法就要多一个类,当策略数量很多时,类的数量会膨胀。比如一个电商系统有 20 种折扣规则,就要有 20 个策略类。

缺点二:所有策略类都需要对外暴露。 客户端必须知道有哪些策略、每个策略的区别是什么,才能选择合适的策略。这增加了客户端的使用成本。

4.3 什么时候该用,什么时候不该用

该用的场景:

  • 一个系统里有多种算法,需要动态切换
  • 需要消除大量 if-elseswitch-case
  • 算法需要独立于客户端进行扩展和复用

不该用的场景:

  • 算法只有一两种,且几乎不会变化(用策略模式是过度设计)
  • 策略类非常简单,只有一两行代码(用 lambda 或函数更合适)

五、策略模式是怎么演进过来的

5.1 起源:建筑领域的模式思想

策略模式的思想根源可以追溯到 20 世纪 70 年代。建筑师克里斯托弗·亚历山大(Christopher Alexander)在他的著作《建筑模式语言》中提出了"模式"的概念:模式是对一个反复出现的问题的解决方案。

他发现,建筑设计中很多问题是重复出现的,比如"如何设计一个采光好的房间",每次都从零开始思考效率太低。于是他把这些常见问题和解决方案整理成模式语言,建筑师可以直接复用。

5.2 引入软件工程:1987 年 OOPSLA 会议

1987 年,在奥兰多举办的 OOPSLA(面向对象编程、系统、语言和应用)会议上,设计模式的概念首次被引入计算机科学领域。Kent Beck 和 Ward Cunningham 发表了关于在软件开发中使用模式的论文,这是软件设计模式的起点。

5.3 正式收录:1994 年 GoF 四人组

1994 年,四位计算机科学家------Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides(被称为 GoF,Gang of Four,四人组)联合出版了软件工程领域的经典著作《设计模式:可复用面向对象软件的基础》。

这本书系统地归纳了 23 种经典面向对象设计模式,策略模式就是其中之一,被归类为行为型模式。这本书的出版标志着设计模式正式成为软件工程的标准知识体系。

5.4 广泛应用:2000 年代 Java 框架

2000 年代,随着 Java 成为企业开发的主流语言,策略模式在各种框架中得到了广泛应用。比如 Spring 框架中的 Resource 接口(不同的资源加载策略)、JDBC 中的 Statement 策略、各种支付网关的多渠道适配等。

5.5 动态语言简化:2010 年代 Python 实现

2010 年代,Python 等动态语言流行起来。在 Python 中,函数是一等公民,可以直接作为参数传递,这让策略模式的实现变得更加简洁。不需要像 Java 那样为每个策略创建一个类,有时候一个函数就够了。

5.6 云原生时代:2020 年代策略治理

进入 2020 年代,微服务和云原生架构普及,策略模式的思想进一步扩展。比如服务网格(Service Mesh)中的流量策略、熔断策略、负载均衡策略,都是策略模式思想在分布式系统中的体现。策略不再只是代码层面的设计模式,更成为了系统治理的核心手段。


六、策略模式怎么用

6.1 基础实现:电商折扣系统

我们以电商折扣系统为例,完整实现一个策略模式。需求是:同一个商品价格,根据不同的用户类型计算不同的折扣价。

第一步:定义策略接口

python 复制代码
from abc import ABC, abstractmethod


class DiscountStrategy(ABC):
    """折扣策略接口"""

    @abstractmethod
    def calculate(self, original_price: float) -> float:
        """计算折扣后的价格"""
        pass

第二步:实现具体策略类

python 复制代码
class NewUserDiscount(DiscountStrategy):
    """新用户折扣:打8折"""

    def calculate(self, original_price: float) -> float:
        return original_price * 0.8


class FullReductionDiscount(DiscountStrategy):
    """满减折扣:满100减20"""

    def calculate(self, original_price: float) -> float:
        if original_price >= 100:
            return original_price - 20
        return original_price


class VIPDiscount(DiscountStrategy):
    """VIP折扣:打7折,再减5元"""

    def calculate(self, original_price: float) -> float:
        return original_price * 0.7 - 5


class NoDiscount(DiscountStrategy):
    """无折扣"""

    def calculate(self, original_price: float) -> float:
        return original_price

第三步:创建上下文类

python 复制代码
class OrderContext:
    """订单上下文,持有当前折扣策略"""

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

    def set_strategy(self, strategy: DiscountStrategy):
        """动态切换折扣策略"""
        self._strategy = strategy

    def calculate_final_price(self, original_price: float) -> float:
        """使用当前策略计算最终价格"""
        return self._strategy.calculate(original_price)

第四步:客户端使用

python 复制代码
if __name__ == "__main__":
    price = 200.0

    # 新用户下单
    context = OrderContext(NewUserDiscount())
    print(f"新用户价: {context.calculate_final_price(price)}")  # 160.0

    # 同一个订单,中途切换为满减策略
    context.set_strategy(FullReductionDiscount())
    print(f"满减价: {context.calculate_final_price(price)}")  # 180.0

    # 切换为VIP策略
    context.set_strategy(VIPDiscount())
    print(f"VIP价: {context.calculate_final_price(price)}")  # 135.0

运行结果:

bash 复制代码
新用户价: 160.0
满减价: 180.0
VIP价: 135.0

可以看到,切换策略只需要调用 set_strategy,不需要修改 OrderContext 的任何代码。

6.2 进阶实现:策略注册中心(企业项目常用写法)

在真实企业项目中,策略类可能有几十个,客户端不可能手动 import 每一个策略类。更好的做法是建立一个策略注册中心,通过名称自动查找策略。

python 复制代码
from typing import Dict, Type


class StrategyRegistry:
    """策略注册中心:自动管理所有策略类"""

    _strategies: Dict[str, Type[DiscountStrategy]] = {}

    @classmethod
    def register(cls, name: str):
        """装饰器:注册策略类"""

        def wrapper(strategy_cls: Type[DiscountStrategy]):
            cls._strategies[name] = strategy_cls
            return strategy_cls

        return wrapper

    @classmethod
    def get_strategy(cls, name: str) -> DiscountStrategy:
        """根据名称获取策略实例"""
        strategy_cls = cls._strategies.get(name)
        if not strategy_cls:
            raise ValueError(f"未找到策略: {name},可用策略: {list(cls._strategies.keys())}")
        return strategy_cls()

    @classmethod
    def list_strategies(cls) -> Dict[str, Type[DiscountStrategy]]:
        """列出所有已注册策略"""
        return cls._strategies.copy()

使用装饰器注册策略:

python 复制代码
@StrategyRegistry.register("new_user")
class NewUserDiscount(DiscountStrategy):
    def calculate(self, original_price: float) -> float:
        return original_price * 0.8


@StrategyRegistry.register("full_reduction")
class FullReductionDiscount(DiscountStrategy):
    def calculate(self, original_price: float) -> float:
        return original_price - 20 if original_price >= 100 else original_price


@StrategyRegistry.register("vip")
class VIPDiscount(DiscountStrategy):
    def calculate(self, original_price: float) -> float:
        return original_price * 0.7 - 5

客户端通过名称获取策略:

python 复制代码
if __name__ == "__main__":
    # 模拟从用户请求中获取折扣类型
    discount_type = "vip"
    price = 200.0

    strategy = StrategyRegistry.get_strategy(discount_type)
    context = OrderContext(strategy)
    final_price = context.calculate_final_price(price)
    print(f"折扣类型: {discount_type}, 最终价格: {final_price}")

    # 查看所有可用策略
    print(f"可用折扣策略: {list(StrategyRegistry.list_strategies().keys())}")

运行结果:

bash 复制代码
折扣类型: vip, 最终价格: 135.0
可用折扣策略: ['new_user', 'full_reduction', 'vip']

这种写法的好处是:新增策略只需要写一个类并加上 @StrategyRegistry.register("名称") 装饰器,注册中心会自动发现,客户端完全不需要改动。

6.3 Python 特色实现:用函数代替类

在 Python 中,函数是一等公民。如果策略逻辑很简单,不需要保存状态,可以直接用函数作为策略,不需要定义类。

python 复制代码
def new_user_discount(price: float) -> float:
    return price * 0.8


def full_reduction_discount(price: float) -> float:
    return price - 20 if price >= 100 else price


def vip_discount(price: float) -> float:
    return price * 0.7 - 5


class FunctionOrderContext:
    """使用函数作为策略的上下文"""

    def __init__(self, strategy_func):
        self._strategy = strategy_func

    def set_strategy(self, strategy_func):
        self._strategy = strategy_func

    def calculate(self, price: float) -> float:
        return self._strategy(price)


if __name__ == "__main__":
    context = FunctionOrderContext(new_user_discount)
    print(context.calculate(200))  # 160.0

    context.set_strategy(vip_discount)
    print(context.calculate(200))  # 135.0

这种写法更加 Pythonic,代码量更少。但如果策略需要保存内部状态(比如支付策略需要保存商户号、API密钥),还是应该用类。

6.4 企业级实战:多渠道支付系统

下面是一个更贴近真实企业项目的例子------多渠道支付系统,包含支付、退款、查询三个方法。

python 复制代码
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional


@dataclass
class PaymentResult:
    """支付结果"""
    success: bool
    transaction_id: str
    message: str
    amount: float


class PaymentStrategy(ABC):
    """支付策略接口"""

    @abstractmethod
    def pay(self, order_id: str, amount: float, **kwargs) -> PaymentResult:
        """支付"""
        pass

    @abstractmethod
    def refund(self, transaction_id: str, amount: float) -> PaymentResult:
        """退款"""
        pass

    @abstractmethod
    def query(self, transaction_id: str) -> PaymentResult:
        """查询订单状态"""
        pass


class AlipayStrategy(PaymentStrategy):
    """支付宝支付策略"""

    def __init__(self, app_id: str, private_key: str):
        self.app_id = app_id
        self.private_key = private_key

    def pay(self, order_id: str, amount: float, **kwargs) -> PaymentResult:
        # 实际项目中这里调用支付宝SDK
        print(f"[支付宝] 订单 {order_id} 支付 {amount} 元")
        return PaymentResult(
            success=True,
            transaction_id=f"ALIPAY{order_id}",
            message="支付成功",
            amount=amount,
        )

    def refund(self, transaction_id: str, amount: float) -> PaymentResult:
        print(f"[支付宝] 订单 {transaction_id} 退款 {amount} 元")
        return PaymentResult(True, transaction_id, "退款成功", amount)

    def query(self, transaction_id: str) -> PaymentResult:
        print(f"[支付宝] 查询订单 {transaction_id}")
        return PaymentResult(True, transaction_id, "订单已支付", 0)


class WechatPayStrategy(PaymentStrategy):
    """微信支付策略"""

    def __init__(self, mch_id: str, api_key: str):
        self.mch_id = mch_id
        self.api_key = api_key

    def pay(self, order_id: str, amount: float, **kwargs) -> PaymentResult:
        openid = kwargs.get("openid", "")
        print(f"[微信支付] 订单 {order_id} 用户 {openid} 支付 {amount} 元")
        return PaymentResult(
            success=True,
            transaction_id=f"WX{order_id}",
            message="支付成功",
            amount=amount,
        )

    def refund(self, transaction_id: str, amount: float) -> PaymentResult:
        print(f"[微信支付] 订单 {transaction_id} 退款 {amount} 元")
        return PaymentResult(True, transaction_id, "退款成功", amount)

    def query(self, transaction_id: str) -> PaymentResult:
        print(f"[微信支付] 查询订单 {transaction_id}")
        return PaymentResult(True, transaction_id, "订单已支付", 0)


class PaymentContext:
    """支付上下文"""

    def __init__(self, strategy: PaymentStrategy):
        self._strategy = strategy

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

    def execute_pay(self, order_id: str, amount: float, **kwargs) -> PaymentResult:
        return self._strategy.pay(order_id, amount, **kwargs)

    def execute_refund(self, transaction_id: str, amount: float) -> PaymentResult:
        return self._strategy.refund(transaction_id, amount)

    def execute_query(self, transaction_id: str) -> PaymentResult:
        return self._strategy.query(transaction_id)


# 策略工厂(结合工厂模式创建策略实例)
class PaymentStrategyFactory:
    """支付策略工厂"""

    @staticmethod
    def create_strategy(method: str) -> PaymentStrategy:
        if method == "alipay":
            return AlipayStrategy(app_id="your_app_id", private_key="your_private_key")
        elif method == "wechat":
            return WechatPayStrategy(mch_id="your_mch_id", api_key="your_api_key")
        else:
            raise ValueError(f"不支持的支付方式: {method}")


if __name__ == "__main__":
    # 用户选择支付宝
    strategy = PaymentStrategyFactory.create_strategy("alipay")
    context = PaymentContext(strategy)
    result = context.execute_pay("ORDER001", 199.0)
    print(f"支付结果: {result.message}, 交易号: {result.transaction_id}")

    # 用户中途改为微信支付
    strategy = PaymentStrategyFactory.create_strategy("wechat")
    context.set_strategy(strategy)
    result = context.execute_pay("ORDER001", 199.0, openid="user_openid_123")
    print(f"支付结果: {result.message}, 交易号: {result.transaction_id}")

运行结果:

bash 复制代码
[支付宝] 订单 ORDER001 支付 199.0 元
支付结果: 支付成功, 交易号: ALIPAYORDER001
[微信支付] 订单 ORDER001 用户 user_openid_123 支付 199.0 元
支付结果: 支付成功, 交易号: WXORDER001

在这个例子中,策略模式和工厂模式结合使用:工厂模式负责创建策略对象,策略模式负责执行具体算法。这是企业项目中非常常见的组合用法。


七、策略模式 vs 竞品对比

策略模式经常和工厂模式、状态模式、模板方法模式混淆,因为它们都涉及到"多种实现"。下面用一张表格彻底说清楚它们的区别。

对比维度 策略模式 工厂模式 状态模式 模板方法模式
模式类型 行为型 创建型 行为型 行为型
核心意图 封装可互换的算法 封装对象的创建过程 状态变化时改变行为 定义算法骨架,子类实现细节
关注点 行为(怎么做) 创建(造哪个) 状态(当前是什么) 流程(先做什么后做什么)
运行时切换 同一个Context可随时切换策略 工厂通常只创建一次对象 状态自动流转,外部不直接切换 模板固定,不能切换
典型场景 折扣算法、支付方式、排序策略 根据类型创建数据源、解析器 订单状态、电梯状态、工单状态 数据库访问流程、报表生成流程
类数量 策略接口 + N个策略类 + Context 工厂接口 + N个产品类 + 工厂类 状态接口 + N个状态类 + Context 抽象模板类 + N个具体子类
生活类比 导航选路线(时间最短/距离最短) 奶茶店点单(根据口味做不同奶茶) 水的三态(固态/液态/气态行为不同) 做菜流程(洗菜→切菜→炒菜,具体菜细节不同)

7.1 策略模式 vs 工厂模式

这是最容易混淆的一对。关键区别:

  • 工厂模式回答"造哪个":根据条件创建一个对象,创建完就结束了。比如根据支付类型创建支付宝对象或微信对象。
  • 策略模式回答"怎么做" :对象已经有了,关注的是这个对象执行什么算法。比如已经有了支付对象,调用它的 pay 方法执行支付。

在实际项目中,两者经常配合使用:工厂模式创建策略对象,策略模式执行算法。上面的支付系统例子就是这种组合。

7.2 策略模式 vs 状态模式

  • 策略模式:策略之间是平等的、可互换的,客户端主动选择用哪个策略。比如折扣策略,新用户折扣和VIP折扣没有先后关系。
  • 状态模式:状态之间有流转关系,状态的切换是由内部条件触发的,客户端不直接控制。比如订单状态从"待支付"到"已支付"到"已发货",有严格的先后顺序。

简单记:策略是"多选一,我来选",状态是"跟着走,自动变"。

7.3 策略模式 vs 模板方法模式

  • 策略模式:用组合实现,每个算法是独立的类,通过注入策略对象来切换。算法之间完全独立,没有公共代码。
  • 模板方法模式:用继承实现,父类定义算法骨架(固定步骤),子类实现可变部分。算法之间共享骨架代码。

如果多个算法有公共的流程步骤,用模板方法;如果算法之间完全独立、需要灵活替换,用策略模式。


八、策略模式常用场景

8.1 电商折扣策略

这是策略模式最经典的应用场景。一个电商系统可能有几十种折扣:新用户折扣、满减、第二件半价、会员折扣、优惠券、限时秒杀......每种折扣的计算逻辑不同,用策略模式可以完美管理。

8.2 支付方式选择

支付宝、微信、银行卡、云闪付、Apple Pay、数字人民币......每种支付方式的接口、参数、回调都不同。用策略模式封装每种支付方式,新增支付渠道只需要加一个策略类。

8.3 导航路径算法

地图软件的路线规划:时间最短、距离最短、避开拥堵、少收费、不走高速......每种规划算法就是一个策略,用户点一下就能切换。

8.4 消息通知渠道

系统需要发送通知时,可以选择短信、邮件、站内信、企业微信、飞书、钉钉等渠道。每个渠道的发送逻辑不同,用策略模式封装后,可以根据用户偏好或消息类型自动选择渠道。

8.5 排序算法切换

一个数据处理系统可能需要根据数据量选择不同的排序算法:数据量小用插入排序,数据量中等用快速排序,数据量巨大用归并排序。策略模式可以在运行时动态选择最优算法。

8.6 压缩格式选择

文件压缩工具支持 ZIP、RAR、7Z、TAR.GZ 等多种格式,每种格式的压缩算法不同。策略模式可以让用户自由选择压缩格式,而不需要修改压缩工具的核心代码。

8.7 日志记录策略

日志系统可以根据环境选择不同的记录策略:开发环境输出到控制台,测试环境输出到文件,生产环境输出到 ELK。策略模式让切换日志策略变得简单。


九、面试官高频面试题

面试题1:请解释一下策略模式,以及你在项目中是怎么用的?

参考答案:

策略模式定义了一系列算法,把每个算法封装成独立的类,使它们可以互相替换。它的核心是将算法的选择和使用分离,客户端可以在运行时动态切换算法,而不需要修改上下文代码。

在我的项目中,我用策略模式重构了订单折扣系统。之前的代码是一个大的 if-elif-else,有十几种折扣规则,每次新增折扣都要改老代码。重构后,我定义了一个 DiscountStrategy 接口,每种折扣是一个实现类,通过策略注册中心自动管理。新增折扣只需要加一个类并加上注册装饰器,老代码完全不用动,代码可维护性大幅提升。

面试题2:策略模式和工厂模式有什么区别?

参考答案:

两者的核心关注点不同:

  • 工厂模式是创建型模式,关注"创建哪个对象"。它根据条件创建不同的对象实例,创建完成后流程就结束了。
  • 策略模式是行为型模式,关注"执行什么算法"。它在对象已经存在的情况下,动态选择不同的行为逻辑。

在实际项目中两者经常配合使用:工厂模式负责创建策略对象,策略模式负责执行具体算法。比如支付系统中,工厂根据支付类型创建支付宝或微信的策略对象,然后上下文调用策略对象的支付方法。

面试题3:策略模式有什么缺点?怎么解决?

参考答案:

策略模式有两个主要缺点:

第一,策略类数量会膨胀。每多一种算法就要多一个类,如果系统有几十种策略,类的数量会很多。解决方法是使用策略注册中心统一管理,或者对于简单策略用函数代替类。

第二,客户端需要了解所有策略的区别才能选择合适的策略。解决方法是封装一个简单工厂或策略选择器,客户端只需要传入条件(如用户类型),由选择器自动返回合适的策略,客户端不需要知道策略的内部细节。

面试题4:策略模式和状态模式怎么区分?

参考答案:

关键区别在于策略之间的关系:

  • 策略模式中,策略之间是平等的、可互换的,没有先后顺序,由客户端主动选择使用哪个策略。比如折扣策略,新用户折扣和VIP折扣是平行的。
  • 状态模式中,状态之间有流转关系,状态的切换是由内部条件自动触发的,客户端不直接控制状态切换。比如订单状态从待支付到已支付到已发货,有严格的先后顺序。

一个简单的判断方法:如果多个实现之间是"多选一,我来选",用策略模式;如果是"跟着走,自动变",用状态模式。

面试题5:Python 中实现策略模式有什么特殊方式?

参考答案:

Python 中函数是一等公民,这让策略模式的实现更加灵活:

  1. 函数作为策略:如果策略逻辑简单、不需要保存状态,可以直接用函数作为策略,不需要定义类,代码更简洁。
  2. 装饰器注册:可以用装饰器自动注册策略类,不需要手动维护策略列表。
  3. 字典映射 :用字典存储策略名称到策略类的映射,通过 key 直接获取策略,比 if-else 更优雅。
  4. Protocol 类型提示 :Python 3.8+ 可以用 typing.Protocol 定义策略接口,不需要继承 ABC,实现更灵活的结构化子类型。

示例:

python 复制代码
from typing import Protocol


class Strategy(Protocol):
    def execute(self, data: str) -> str: ...


def strategy_a(data: str) -> str:
    return f"A处理: {data}"


def strategy_b(data: str) -> str:
    return f"B处理: {data}"


# 字典映射策略
strategies = {
    "A": strategy_a,
    "B": strategy_b,
}

result = strategies["A"]("测试数据")

面试题6:什么时候不应该使用策略模式?

参考答案:

以下情况不建议使用策略模式:

  1. 算法只有一两种,且未来几乎不会变化。这时候用策略模式是过度设计,增加了代码复杂度。
  2. 策略逻辑非常简单,只有一两行代码。这时候用 lambda 或简单的函数参数更合适。
  3. 客户端不需要动态切换算法。如果算法在编译时就确定了,不需要运行时切换,策略模式的优势就不明显。
  4. 策略之间有大量公共代码。这时候更适合用模板方法模式,把公共代码抽到父类中。

十、总结

策略模式是设计模式中最实用、最常用的模式之一。它的核心思想非常简单:把变化的部分封装起来,让它们可以互相替换。

回顾本文的核心要点:

  1. 解决的问题 :消灭 if-else 地狱,让算法可以自由切换和扩展
  2. 三个角色:抽象策略接口、具体策略类、上下文Context
  3. 最大优势:符合开闭原则,新增算法不需要修改老代码
  4. 企业常用写法:策略注册中心 + 装饰器自动注册 + 工厂模式组合
  5. Python特色:函数可作为策略,代码更简洁
  6. 易混淆区分:策略关注"怎么做",工厂关注"造哪个",状态关注"自动变",模板关注"流程骨架"

掌握策略模式,不仅能让你的代码更优雅,也是面试中的加分项。下次再看到一大片 if-else 的时候,想想是不是该用策略模式重构了。


转载声明:本文为原创文章,如需转载,请联系作者获得授权,并注明出处。

相关推荐
旖旎夜光1 小时前
【LangChain实战】LangChain 学习笔记(一):从定义大模型到工具调用
人工智能·笔记·python·学习·langchain
临沂GEO1 小时前
用好地域流量,提升内容自然搜索曝光
网络·python
问天_观心10 小时前
大模型微调学习(二)
人工智能·python·深度学习·学习·语言模型·transformer
洋洋不叫杨杨10 小时前
揭秘当下知名的SEO优化渠道,你知道几个?
大数据·python
阿童木写作10 小时前
跨境图片翻译工具推荐:批量处理视频字幕与智能抠图
python·音视频
梦想的颜色10 小时前
OCR 识别原理与 Python 识图全实战:从文字提取到图像内容理解
python·计算机视觉·ocr·图像识别·python 识图
禹凕10 小时前
Dijkstra算法详解与应用
python·算法
tangwangbi10 小时前
Linux 系统配置文件:/etc/profile、~/.bashrc 和 ~/.bash_profile 三者之间的区别与作用
linux·运维·bash
蜀道山老天师11 小时前
Shell Bash变量与运算符(含条件测试与流程控制)
linux·运维·bash