在上篇文章《项目代码太多if-else?这样优化才优雅!》中,我们已经介绍了优化if-else条件逻辑的7个实用技巧。本文接着上篇文章的主题,以某电商平台618活动订单优惠计算逻辑优化为例,和大家分享使用策略模式优化if-else代码块的方法。
这个需求很简单
一大早我的咖啡还没喝完,就被产品拉去开会提需求了,产品给出了平台今年618大促的折扣规则:
•88Vip 95折•每满300减40•买3件免单1件
她给我提了一个需求,让我把这些优惠活动的逻辑加到订单结算系统中,如果顾客参与了某个活动,在支付的时候,就要减去这些折扣。
这个实现好简单
这个简单啊!不就是加几个if-else
,根据不用的优惠方式计算折扣么?我一顿操作5分钟写好了代码!
我先定义了一个顾客和产品类:
from collections import namedtuple
Customer = namedtuple('Customer', 'name is_88vip')
Product = namedtuple('Product', 'name price')
因为顾客和产品是纯数据类,只有属性没有任何方法,所以我使用了collections
模块下的namedtuple
来实现。
接下来是重点内容了,我设计了一个订单类,订单信息中包含顾客名,购物清单,以及该订单享受的优惠活动。
class Order:
def __init__(self, customer, cart, promotion=None):
"""订单类
Args:
customer (Customer): 该订单的顾客
cart (list): 购物车列表
promotion (str, optional): 该订单享受的优惠,默认为 None
"""
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
"""订单优惠前总额"""
if not hasattr(self, '__total'):
self.__total = sum(item.price for item in self.cart)
return self.__total
def due(self):
"""订单优惠后总额"""
if self.promotion is None:
discount = 0
# 88vip打95折
elif self.promotion=='vip_discount':
discount = self.total() * .05 if self.customer.is_88vip else 0
# 每满300减40
elif self.promotion=='full_credit_discount':
total = 0
for item in self.cart:
total += item.price
discount = (total//300) * 40
# 满3减免单一件
elif self.promotion=='free_one_discount':
if len(self.cart) >= 3:
discount = min([item.price for item in self.cart])
else:
discount = 0
return self.total() - discount
def __repr__(self):
fmt = '<订单 总价: {:.2f} 实付: {:.2f}>'
return fmt.format(self.total(), self.due())
那么怎么计算优惠呢?
我在Order
类中加了一个due()
方法,该方法中有一大段的if-else
语句,根据不同的优惠策略,计算订单优惠后的总额。比如如果是88vip, 就在原来总额的基础上打95折。
很快代码就写好了,我就交付上线了。
客户端调用:
joe = Customer('John Doe', True)
cart_A = [Product('banana', 4),
Product('apple', 10),
Product('watermellon', 5)]
print('策略一:为88vip顾客提供5%折扣')
print(Order(joe, cart_A, "vip_discount"))
cart_B = [Product('banana', 630),
Product('apple', 410)]
print('策略二:每满300减40活动')
print(Order(joe, cart_B, "full_credit_discount"))
cart_C = [Product('banana', 630),
Product('apple', 410),
Product('watermellon', 210)]
print('策略三:满3件免单最便宜的一件')
print(Order(joe, cart_C, "free_one_discount"))
运行结果:
(base) root\Python> main.py
策略一:为88vip顾客提供5%折扣
<订单 总价: 19.00 实付: 18.05>
策略二:每满300减40活动
<订单 总价: 1040.00 实付: 920.00>
策略三:满3件免单最便宜的一件
<订单 总价: 1250.00 实付: 1040.00>
改需求了!
过了2天,产品又过来找我了,说市场部又策划了30个优惠活动,你再把优惠计算逻辑这些加上去吧!(发个图大家感受下!)
我打开代码,准备无脑加几个if-else
的时候,突然意识到一件很严重的问题:
我这次改好if-else
, 如果哪天又上了新的优惠策略,岂不是我又要加一个if-else
?鬼知道她们还会不会天天改需求,现在这样子做根本没有扩展性。这不符合设计模式中的对扩展开放,对修改关闭的原则。
我隔壁的同事小王说,加一个if-else
也没有什么大不了的事情呀?不用那复杂吧?
但是,目前我们只有3个优惠策略,一个方法的代码已经20行了,如果我们有30个策略,这个方法可能就要300行了!从另外一个角度讲,这不符合单一职责原则。
所以,考虑到系统后期的扩展,和代码的测试和维护,我决定重新设计一下我的代码了。
使用策略模式拯救if-else
我发现Order
类的due()
这个方法的if-else
逻辑实在是太多了,每个if-else
都是一种具体的折扣逻辑,他们完成的是同样的一件事情——计算折扣,只是具体行为有些差异而已,所以这是典型的策略模式的应用场景啊!
因此第一步我需要将这些具体的优惠策略提炼为独立的函数。
def vip_discount(order):
"""淘宝88vip客户95折"""
return order.total() * .05 if order.customer.is_88vip else 0
def full_credit_discount(order):
"""每满300减40"""
total = 0
for item in order.cart:
total += item.price
discount = (total//300) * 40
return discount
def free_one_discount(order):
"""满3件商品免单最便宜的一件"""
if len(order.cart) >= 3:
return min([item.price for item in order.cart])
return 0
这些方法都接受一个Order
对象作为参数,并且使用Order
对象提供的信息,决定如何计算订单的优惠。我们把这些参数提炼到独立的函数以后,下面就可以着手去修改Order
类的due()
方法了。
class Order:
def __init__(self, customer, cart, promotion=None):
"""订单类
Args:
customer (Customer): 该订单的顾客
cart (list): 购物车列表
promotion (function, optional): 该订单折扣的计算方法
"""
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
"""订单优惠前总额"""
if not hasattr(self, '__total'):
self.__total = sum(item.price 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())
Order
类的改动也不复杂,因为在Python中,函数可以作为参数对象进行传递,所以现在的Order
类的promotion
参数不再接受一个字符串对象了,改为接收一个订单优惠计算策略的函数对象。然后,我在due()
方法中,可以直接引用通过promotion
传入的函数对象,来实现订单优惠的计算了。
下面是客户端的调用代码:
joe = Customer('John Doe', True)
cart_A = [Product('banana', 4),
Product('apple', 10),
Product('watermellon', 5)]
print('策略一:为88vip顾客提供5%折扣')
print(Order(joe, cart_A, vip_discount))
cart_B = [Product('banana', 630),
Product('apple', 410)]
print('策略二:每满300减40活动')
print(Order(joe, cart_B, full_credit_discount))
cart_C = [Product('banana', 630),
Product('apple', 410),
Product('watermellon', 210)]
print('策略三:满3件免单最便宜的一件')
print(Order(joe, cart_C, free_one_discount))
运行结果:
(base) root\Python> main.py
策略一:为88vip顾客提供5%折扣
<订单 总价: 19.00 实付: 18.05>
策略二:每满300减40活动
<订单 总价: 1040.00 实付: 920.00>
策略三:满3件免单最便宜的一件
<订单 总价: 1250.00 实付: 1040.00>
总结
在本篇文章中,我们主要的目的是讨论使用策略模式优化if-else
逻辑的方法,当然,使用策略模式的理由远不仅仅是 为了优化if-else
。在所有设计模式中,策略模式是最简单也是应用最广的一种设计模式,在实际应用中,策略模式还适用以下场景:
•如果有多种方式完成同一件事情,那就是策略模式大展拳脚的时候了。比如我们的示例中有多种优惠计算方法。•一个类或者方法中有太多的if-else
代码逻辑。这通常暗示着这个类或者方法承担了太多的职责,违反了软件设计中的单一职责原则,使用策略模式,将可以帮助我们将这些职责分离开来。•最后,使用策略模式还可以帮助我们解耦行为和使用这些行为的类,便于扩展和维护。