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 日志记录策略)
- 九、面试官高频面试题
-
- 面试题1:请解释一下策略模式,以及你在项目中是怎么用的?
- 面试题2:策略模式和工厂模式有什么区别?
- 面试题3:策略模式有什么缺点?怎么解决?
- 面试题4:策略模式和状态模式怎么区分?
- [面试题5:Python 中实现策略模式有什么特殊方式?](#面试题5:Python 中实现策略模式有什么特殊方式?)
- 面试题6:什么时候不应该使用策略模式?
- 十、总结
一、痛点场景:被 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 生活中的类比
这就像你去餐厅吃饭,菜单上每加一道菜,厨师就要重新写一遍菜谱。本来菜谱应该是每道菜单独一页,结果全挤在一张纸上,改一道菜就要重写整张纸。
策略模式要做的,就是把每道菜的菜谱拆出来,单独放一页,想做哪道菜就翻哪一页。
二、痛点的解决方案:把算法拆出来
策略模式的核心思想只有一句话:把每个算法(策略)封装成独立的类,让它们可以互相替换。
回到支付的例子,策略模式会这样做:
- 定义一个支付策略接口,规定所有支付方式都必须实现
pay方法 - 支付宝、微信、银行卡各自写成一个独立的策略类
- 定义一个上下文类(Context),它持有当前策略的引用,负责调用策略
- 客户端想换支付方式?直接给上下文换一个策略对象就行,不用改任何老代码
这样一来,新增支付方式只需要加一个新类,老代码一行都不用动。这就是开闭原则的完美体现。
三、策略模式是什么

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-else或switch-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 中函数是一等公民,这让策略模式的实现更加灵活:
- 函数作为策略:如果策略逻辑简单、不需要保存状态,可以直接用函数作为策略,不需要定义类,代码更简洁。
- 装饰器注册:可以用装饰器自动注册策略类,不需要手动维护策略列表。
- 字典映射 :用字典存储策略名称到策略类的映射,通过 key 直接获取策略,比
if-else更优雅。 - 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:什么时候不应该使用策略模式?
参考答案:
以下情况不建议使用策略模式:
- 算法只有一两种,且未来几乎不会变化。这时候用策略模式是过度设计,增加了代码复杂度。
- 策略逻辑非常简单,只有一两行代码。这时候用 lambda 或简单的函数参数更合适。
- 客户端不需要动态切换算法。如果算法在编译时就确定了,不需要运行时切换,策略模式的优势就不明显。
- 策略之间有大量公共代码。这时候更适合用模板方法模式,把公共代码抽到父类中。
十、总结
策略模式是设计模式中最实用、最常用的模式之一。它的核心思想非常简单:把变化的部分封装起来,让它们可以互相替换。
回顾本文的核心要点:
- 解决的问题 :消灭
if-else地狱,让算法可以自由切换和扩展 - 三个角色:抽象策略接口、具体策略类、上下文Context
- 最大优势:符合开闭原则,新增算法不需要修改老代码
- 企业常用写法:策略注册中心 + 装饰器自动注册 + 工厂模式组合
- Python特色:函数可作为策略,代码更简洁
- 易混淆区分:策略关注"怎么做",工厂关注"造哪个",状态关注"自动变",模板关注"流程骨架"
掌握策略模式,不仅能让你的代码更优雅,也是面试中的加分项。下次再看到一大片 if-else 的时候,想想是不是该用策略模式重构了。
转载声明:本文为原创文章,如需转载,请联系作者获得授权,并注明出处。