你是否曾经打开过食品配送应用,在“最快路线”、“最便宜选项”或“停站次数最少”之间进行选择?又或者在结账时选择信用卡、PayPal或钱包余额等支付方式?实际上,在这两种情况下,很可能会用到策略模式。
策略模式允许你定义一组算法,将每种算法放在不同的类中,并让它们在运行时可以互相替换。每当需要改变程序的行为时,你不必每次都编写冗长的if/elif代码链,只需更换相应的策略即可实现目标。
在本教程中,你将了解什么是策略模式、它为什么有用,以及如何通过实际例子在Python中实现它。
你可以在GitHub上获取相关代码。
先决条件
在开始之前,请确保你已经满足以下要求:
-
已安装Python 3.10或更高版本
-
对Python类和方法有基本了解
-
熟悉面向对象编程的概念
那我们开始吧!
目录
什么是策略模式?
策略模式是一种将一组相关的算法封装起来、使它们能够互相替换的设计模式。使用这些算法的对象被称为上下文,它无需了解具体算法的实现细节,只需将任务委托给当前被选中的策略即可。
想象一下GPS应用:目标地点是固定的,但你可以选择“避开高速公路”、“走最短路线”或“选择交通最少的路线”,而这些选项本身并不影响最终的目的地或应用程序的结构。每种路线选择方式其实就是一种不同的策略。
当遇到以下情况时,策略模式会非常有用:
-
当你需要实现算法的多种变体时
-
当你希望避免基于类型编写冗长的
if/elif条件语句时 -
当你希望在运行时改变程序的行为,而不修改上下文类时
-
当应用程序的不同部分需要使用同一操作的多种变体时
现在让我们通过一些例子来进一步理解这个概念。
简单的策略模式示例
我们来构建一个简单的电子商务订单系统,在结账时可以应用不同的折扣策略。
首先,我们创建三种折扣策略:
class RegularDiscount:
def apply(self, price):
return price * 0.95 # 享受5%的折扣
class SeasonalDiscount:
def apply(self, price):
return price * 0.80 # 享受20%的折扣
class NoDiscount:
def apply(self, price):
return price # 不进行任何折扣处理
每个类都包含一个名为apply的方法,该方法接受一个价格参数,并返回折扣后的价格。这些方法遵循相同的接口,但实现了不同的逻辑——这就是策略模式中的关键概念。
现在让我们创建一个Order类,让它使用其中某种折扣策略:
class Order:
def __init__(self, product, price, discount_strategy):
self.product = product
self.price = price
self.discount_strategy = discount_strategy
def final_price(self):
return self.discount_strategy.apply(self.price)
def summary(self):
print(f"商品名称 : {self.product}")
print(f>原价 : ${self.price:.2f}")
print(f>折扣后价格 : ${self.final_price():.2f}")
print("-" * 30)
Order类就充当了我们的上下文。它本身并不包含任何折扣计算逻辑——所有这些功能都由discount_strategy.apply()方法来完成。无论你传递哪个策略对象,都会被用来执行相应的折扣计算。
现在我们来下达一些订单:
order1 = Order("机械键盘", 120.00, NoDiscount())
order2 = Order("笔记本电脑支架", 45.00, RegularDiscount())
order3 = Order("USB-C集线器", 35.00, SeasonalDiscount())
order1.summary()
order2.summary()
order3.summary()
运行上述代码后,你将会得到如下输出:
商品名称 : 机械键盘
原价 : $120.00
折扣后价格 : $120.00
------------------------------
商品名称 : 笔记本电脑支架
原价 : $45.00
折扣后价格 : $42.75
------------------------------
商品名称 : USB-C集线器
原价 : $35.00
折扣后价格 : $28.00
------------------------------
请注意,Order类从未执行过if discount_type == "seasonal"这样的判断。它只是简单地调用apply()方法,然后将折扣计算的任务交给相应的策略对象来处理。今后如果需要添加新的折扣类型,只需要创建一个新的类即可,其他代码无需做任何修改。
在运行时切换策略
策略模式的最大优势之一就是:你可以在程序运行过程中随时更改所使用的策略。比如,假设用户在购物过程中升级为了高级会员:
class ShoppingCart:
def __init__(self):
self.items = []
self.discount_strategy = NoDiscount() # 默认策略
def add_item(self, name, price):
self.items.append({"name": name, "price": price})
def set_discount(self, strategy):
self.discount_strategy = strategy
print(f>折扣策略已更改为:{strategy.__class__.__name__}")
def checkout(self):
print("\n--- 结账摘要 ---")
total = 0
for item in self.items:
discounted = self.discount_strategy.apply(item["price"])
print(f"{item['name']}: ${discounted:.2f}")
total += discounted
print(f>总金额 : ${total:.2f}\n")
set_discount方法允许我们在任何时候更改所使用的折扣策略。让我们来看一下这个方法的实际应用效果:
cart = ShoppingCart()
cart.add_item("Notebook", 15.00)
cart.add_item("Desk Lamp", 40.00)
cart.add_item("Monitor Riser", 25.00)
# 以普通顾客的身份进行结账
cart.checkout()
# 用户升级为季节性促销会员
cart.set_discount(SeasonalDiscount())
cart.checkout()
运行上述代码后,将会得到如下输出:
--- 结账摘要 ---
笔记本:15.00美元
台灯:40.00美元
显示器支架:25.00美元
总金额:80.00美元
折扣类型已更改为:季节性促销折扣
--- 结账摘要 ---
笔记本:12.00美元
台灯:32.00美元
显示器支架:20.00美元
总金额:64.00美元
实际上,购物车本身并没有发生任何变化,只有所使用的折扣策略发生了改变。这就是将“行为逻辑”与使用该逻辑的“上下文环境”分开处理的优点所在。
使用抽象基类
到目前为止,还没有任何规定要求所有的折扣策略都必须具备apply方法。如果有人创建了一个折扣策略却忘记了实现这个方法,那么在运行时他们就会遇到AttributeError错误。我们可以利用Python的抽象基类来避免这种情况。
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def apply(self, price: float) -> float:
pass
现在,让我们将所有的折扣策略都改写为继承自
class RegularDiscount(DiscountStrategy):
def apply(self, price):
return price * 0.95
class SeasonalDiscount(DiscountStrategy):
def apply(self, price):
return price * 0.80
class NoDiscount(DiscountStrategy):
def apply(self, price):
return price
如果有人尝试创建一个没有实现apply方法的折扣策略,Python会在他们尝试实例化这个类时立即抛出TypeError错误——甚至在任何代码开始执行之前就会发生这种错误。这样的错误处理方式要清晰得多。
class BrokenStrategy(DiscountStrategy):
pass # 忘了实现apply()方法
s = BrokenStrategy() # 这里会立即抛出TypeError错误
在大型团队或共享代码库中,使用抽象基类尤其有用。因为这样就可以明确规定:所有的折扣策略必须实现apply方法。否则,就会出现像上面示例中所示的错误。
2 pass # 忘了实现apply()方法
3
----> 4 s = BrokenStrategy() // 这里会立即抛出TypeError错误
TypeError: 无法实例化抽象类BrokenStrategy,因为没有实现抽象方法'apply'
何时使用策略模式
在以下情况下,策略模式是非常适用的:
-
当你需要根据不同的类型来执行不同的操作时——那些冗长的
if/elif语句,用于判断某个“模式”或“类型”,这时策略模式就能派上用场。 -
当行为需要在运行时进行动态调整时——比如用户设置或配置参数的变化能够导致算法的切换,而无需重新启动程序。
-
当你正在构建可扩展的系统时——新的功能可以通过添加新的类来实现,而无需修改现有的代码。
-
当你希望独立地测试各种算法时——每个策略都对应一个独立的类,这样进行单元测试就会变得非常方便。
在以下情况下应避免使用这种模式:
-
当你只有两种永远不会发生变化的情况时,使用简单的
if/else语句就完全足够了。 -
当这些策略需要共享大量状态时,将它们分开成不同的类只会增加代码的复杂性,而并不会带来任何实际好处。
总结
希望这篇教程对您有所帮助。总之,策略模式为您提供了一种简洁的方法来管理不同的行为方式,同时避免了让类中充斥过多的条件逻辑。这样,代码的结构就能保持简单和稳定,而具体的处理逻辑则由相应的策略类来负责。
我们介绍了这种模式的基本原理、运行时策略的切换机制,以及如何利用抽象基类来确保各种策略之间的兼容性。与大多数设计模式一样,开始时应该从最简单的形式入手:即使不使用抽象基类,将算法分别放在不同的类中,也会使代码更易于阅读、测试和扩展。
祝您编程愉快!