Python策略模式实战:从类实现到函数优化的演进
本文深入解析Python中的策略模式,通过电商折扣计算案例,对比传统面向对象实现与利用Python高阶函数特性的简化实现。文章指出在Python中,普通函数可作为轻量级策略对象,无需创建额外类即可实现算法替换,从而降低代码复杂度与维护成本,同时探讨了策略模式与享元模式的结合使用场景。
尽管设计模式本身具有语言无关性,但这并不意味着所有模式都能无缝适配每一种编程语言。在经典著作《设计模式:可复用面向对象软件的基础》所列举的23种模式中,有16种在动态语言环境中要么不再适用,要么被大幅简化。
策略模式概述与电商案例
1、策略模式概述
策略模式的核心在于定义一系列算法,将它们分别封装,并确保这些算法之间可以互相替换。这种设计使得算法本身的变更不会对调用算法的客户代码产生任何影响。
在电子商务领域,策略模式有一个经典应用场景:根据客户属性或订单中商品的具体情况进行折扣计算。
假设某网店制定了以下具体的折扣规则:
- 对于积分达到1000或以上的顾客,其订单可享受5%的折扣。
- 若同一订单中单个商品的数量达到20个或以上,则享受10%的折扣。
- 若订单中包含的不同商品种类达到10种或以上,则享受7%的折扣。
为了简化示例,我们假设一个订单在同一时间内只能应用一种折扣策略。
其UML类图结构如下:

Promotion抽象类定义了各类算法的公共接口,而fidelityPromo、BulkPromo和LargeOrderPromo这三个子类则实现了具体的“策略”。在实际应用中,具体由上下文类中的客户决定选用哪一种策略。
在该示例中,在实例化Order类之前,系统会以某种机制选定一种促销折扣策略,并将其传递给Order的构造方法。至于具体如何选择策略,并不属于策略模式本身的职责范围(例如,可以使用工厂模式来实现策略的选择)。
传统类实现与函数优化对比
2、传统方法实现策略模式:
from abc import ABC, abstractmethod
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:
"""订单中单个商品的数量和单价"""
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
def total(self):
return self.price * self.quantity
class Order:
"""订单"""
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
if not hasattr(self, '__total'):
self.__total = sum(item.total() for item in self.cart)
return self.__total
def due(self):
if self.promotion is None:
discount = 0
else:
discount = self.promotion.discount(self)
return self.total() - discount
def __repr__(self):
fmt = '<订单 总价: {:.2f} 实付: {:.2f}>'
return fmt.format(self.total(), self.due())
class Promotion(ABC): # 策略:抽象基类
@abstractmethod
def discount(self, order):
"""返回折扣金额(正值)"""
class FidelityPromo(Promotion): # 第一个具体策略
"""为积分为1000或以上的顾客提供5%折扣"""
def discount(self, order):
return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
class BulkItemPromo(Promotion): # 第二个具体策略
"""单个商品为20个或以上时提供10%折扣"""
def discount(self, order):
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * 0.1
return discount
class LargeOrderPromo(Promotion): # 第三个具体策略
"""订单中的不同商品达到10个或以上时提供7%折扣"""
def discount(self, order):
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * 0.07
return 0
joe = Customer('John Doe', 0)
ann = Customer('Ann Smith', 1100)
cart = [LineItem('banana', 4, 0.5),
LineItem('apple', 10, 1.5),
LineItem('watermellon', 5, 5.0)]
print('策略一:为积分为1000或以上的顾客提供5%折扣')
print(Order(joe, cart, FidelityPromo()))
print(Order(ann, cart, FidelityPromo()))
banana_cart = [LineItem('banana', 30, 0.5),
LineItem('apple', 10, 1.5)]
print('策略二:单个商品为20个或以上时提供10%折扣')
print(Order(joe, banana_cart, BulkItemPromo()))
long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]
print('策略三:订单中的不同商品达到10个或以上时提供7%折扣')
print(Order(joe, long_order, LargeOrderPromo()))
print(Order(joe, cart, LargeOrderPromo()))
运行结果如下:
策略一:为积分为1000或以上的顾客提供5%折扣 <订单 总价: 42.00 实付: 42.00> <订单 总价: 42.00 实付: 39.90> 策略二:单个商品为20个或以上时提供10%折扣 <订单 总价: 30.00 实付: 28.50> 策略三:订单中的不同商品达到10个或以上时提供7%折扣 <订单 总价: 10.00 实付: 9.30> <订单 总价: 42.00 实付: 42.00>
3、使用函数实现策略模式
在传统策略模式中,每个具体策略都是一个独立的类,且通常只定义了一个方法,没有其他实例属性。从外观上看,它们与普通函数无异。事实上,在Python中,我们可以直接用简单的函数替换具体策略类,并彻底移除策略的抽象类。
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
def total(self):
return self.price * self.quantity
class Order:
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
if not hasattr(self, '__total'):
self.__total = sum(item.total() for item in self.cart)
return self.__total
def due(self):
if self.promotion is None:
discount = 0
else:
discount = self.promotion(self)
return self.total() - discount
def __repr__(self):
fmt = '<订单 总价: {:.2f} 实付: {:.2f}>'
return fmt.format(self.total(), self.due())
def fidelity_promo(order):
"""为积分为1000或以上的顾客提供5%折扣"""
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
def bulk_item_promo(order):
"""单个商品为20个或以上时提供10%折扣"""
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
def large_order_promo(order):
"""订单中的不同商品达到10个或以上时提供7%折扣"""
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0
joe = Customer('John Doe', 0)
ann = Customer('Ann Smith', 1100)
cart = [LineItem('banana', 4, 0.5),
LineItem('apple', 10, 1.5),
LineItem('watermellon', 5, 5.0)]
print('策略一:为积分为1000或以上的顾客提供5%折扣')
print(Order(joe, cart, fidelity_promo))
print(Order(ann, cart, fidelity_promo))
banana_cart = [LineItem('banana', 30, 0.5),
LineItem('apple', 10, 1.5)]
print('策略二:单个商品为20个或以上时提供10%折扣')
print(Order(joe, banana_cart, bulk_item_promo))
long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]
print('策略三:订单中的不同商品达到10个或以上时提供7%折扣')
print(Order(joe, long_order, large_order_promo))
print(Order(joe, cart, large_order_promo))
实际上,任何支持高阶函数的语言都可以采用这种实现方式,例如在C#中可以使用委托来实现。然而,这样做反而可能导致代码变得复杂且难以理解。相比之下,Python中函数天然支持作为参数传递,使得这种实现更加自然。
值得注意的是,《设计模式:可复用面向对象软件的基础》一书的作者指出:“策略对象通常是很好的享元。”享元是一种可共享的对象,可以在多个上下文中同时使用。推荐的做法是进行共享,这样在每次使用相同策略处理新的上下文(即Order实例)时,无需不断新建具体策略对象,从而减少资源消耗。因此,为了避免[策略模式]带来的运行时开销,可以配合[享元模式]一起使用。但这样做会导致代码行数和维护成本不断增加。
在复杂场景下,当具体策略需要维护内部状态时,可能需要将“策略”和“享元”模式结合使用。然而,具体策略通常不涉及内部状态,仅处理上下文中的数据。此时,务必使用普通函数,避免编写仅包含一个方法的类再去实现另一个类声明的单函数接口。函数比用户自定义类的实例更轻量,且无需使用“享元”模式,因为各个策略函数在Python编译模块时仅会被创建一次。普通的函数本身就是“可共享的对象,可以同时在多个上下文中使用”。
以上就是详解Python设计模式之策略模式的详细内容,更多关于Python 策略模式的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









