Python設(shè)計模式中的策略模式詳解
策略模式
策略模式是一個經(jīng)典的模式,簡化代碼。
電商領(lǐng)域有個功能明細可以使用“策略”模式,就是根據(jù)客戶的屬性或訂單中的商品計算折扣。
比如一個網(wǎng)店,指定了以下的折扣規(guī)則, 并且一個訂單只能享受一個折扣:
- 有1000積分以上的顧客,整個訂單可以享受5%的折扣
- 同一個訂單中,單個商品的數(shù)量達到20個以上,單品享受10%折扣
- 訂單中不同商品的數(shù)量達到10個以上,整個訂單享受7%折扣
下面是UML類圖:
上下文:把一些計算委托給實現(xiàn)不同算法的可互換組建,他們提供服務(wù)。 在這個示例中,上下文就是Order,根據(jù)不同算法提供折扣。
策略:實現(xiàn)不同算法的組件共同接口。在這個示例中,Promotion的抽象類扮演這個角色。
具體策略:“策略”的子類,實現(xiàn)具體策略
示例,實現(xiàn)Order類,支持插入式折扣策略
import abc from collections import namedtuple from abc import abstractmethod Customer = namedtuple('Customer', 'name fidelity') # name:顧客名字 fidelity:忠誠度,這里用積分體現(xiàn) class LineItem: """單品信息""" def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): return self.quantity * self.price 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): return '<Order total:{:.2f} due:{:.2f}>'.format(self.total(), self.due()) # {:.2f}表示輸出小數(shù)點,保留2位小數(shù) class Promotion(abc.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): """具體策略:同一個訂單中,單個商品的數(shù)量達到20個以上,單品享受10%折扣""" def discount(self, order): # return order.total() * 0.1 if any(item for item in order.cart if item.quantity >= 20) else 0 理解錯誤為整體折扣 discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * .1 return discount class LargeOrderPromo(Promotion): """具體策略:訂單中不同商品的數(shù)量達到10個以上,整個訂單享受7%折扣""" def discount(self, order): return order.total() * 0.07 if len({item.product for item in order.cart}) >= 10 else 0
聊一下抽象基類:
Python3.4中,聲明抽象基類的最簡單的方式是繼承abc.ABC :class Promotion(abc.ABC):
Python3.0到Python3.3中,必須在class中使用metaclass=關(guān)鍵字 : class Promotion(metaclass=abc.ABCMeta):
Python2中,要在 類屬性中增加__metaclass__ = abc.ABCMeta
測試代碼
# 兩個顧客,一個積分0,一個積分1100 joe = Customer('Joe', 0) ann = Customer('Ann', 1100) # 有三個商品的購物車 cart = [LineItem('banana', 4, .5), LineItem('apple', 10, 1.5), LineItem('watermelon', 5, 5.0)] # ann因為超過1000積分,獲得了5%折扣 order_joe = Order(joe, cart, FidelityPromo()) #這里要FidelityPromo要加()來創(chuàng)建實例 print(order_joe) order_ann = Order(ann, cart, FidelityPromo()) print(order_ann) # 香蕉30個,蘋果10個。 banana_cart = [LineItem('banana', 30, .5), LineItem('apple', 10, 1.5)] # joe因為香蕉有30個,根據(jù)BulkItemPromo 香蕉優(yōu)惠了1.5元 order_joe = Order(joe, banana_cart, BulkItemPromo()) print(order_joe)
打印
<Order total:42.00 due:42.00>
<Order total:42.00 due:39.90>
<Order total:30.00 due:28.50>
以上的模式中,每個具體策略都是一個類,而且只定義了一個方法:discount。此外他們的策略示例沒有實例屬性,看起來就像普通函數(shù)。
示例,使用函數(shù)實現(xiàn)折扣策略
Customer = namedtuple('Customer', 'name fidelity') # name:顧客名字 fidelity:忠誠度,這里用積分體現(xiàn) class LineItem: """單品信息""" def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): return self.quantity * self.price 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): return '<Order total:{:.2f} due:{:.2f}>'.format(self.total(), self.due()) # {:.2f}表示輸出小數(shù)點,保留2位小數(shù) def FidelityPromo(order): """具體策略:有1000積分以上的顧客,整個訂單可以享受5%的折扣""" return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0 def BulkItemPromo(order): """具體策略:同一個訂單中,單個商品的數(shù)量達到20個以上,單品享受10%折扣""" discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * .1 return discount def LargeOrderPromo(order): """具體策略:訂單中不同商品的數(shù)量達到10個以上,整個訂單享受7%折扣""" return order.total() * 0.07 if len({item.product for item in order.cart}) >= 10 else 0 # 兩個顧客,一個積分0,一個積分1100 joe = Customer('Joe', 0) ann = Customer('Ann', 1100) # 有三個商品的購物車 cart = [LineItem('banana', 4, .5), LineItem('apple', 10, 1.5), LineItem('watermelon', 5, 5.0)] # ann因為超過1000積分,獲得了5%折扣 order_joe = Order(joe, cart, FidelityPromo) print(order_joe) order_ann = Order(ann, cart, FidelityPromo) print(order_ann) # 香蕉30個,蘋果10個。 banana_cart = [LineItem('banana', 30, .5), LineItem('apple', 10, 1.5)] # joe因為香蕉有30個,根據(jù)BulkItemPromo 香蕉優(yōu)惠了1.5元 order_joe = Order(joe, banana_cart, BulkItemPromo) print(order_joe)
打印
<Order total:42.00 due:42.00>
<Order total:42.00 due:39.90>
<Order total:30.00 due:28.50>
以上可以看到,使用函數(shù)更加簡單,代碼量減少。沒必要在新建訂單實例化新的促銷對象,函數(shù)拿來即用。
選擇最佳策略
要實現(xiàn)最佳折扣策略的自動選擇,只需要一個額外的函數(shù)即可。這樣就能自動找出最高的折扣。
def best_promotion(order): promotions = [FidelityPromo, BulkItemPromo, LargeOrderPromo] return max([func(order) for func in promotions])
自動找出模塊中的全部策略
以上的promotions列表包含了三個策略,當再新增新的策略時,需要在這個列表中追加,使用以下寫法,可以自動識別策略函數(shù):
示例,實現(xiàn)了把Promo結(jié)尾的函數(shù)引用,放進promotions列表中
def best_promotion(order): promotions = [globals()[key] for key in list(globals().keys()) if key.endswith('Promo')] return max([func(order) for func in promotions])
以上實現(xiàn)的原理就是利用globals()內(nèi)置函數(shù):
globals()返回一個字典,表示當前的全局符號表。包含了當前所有定義的函數(shù)等。
自動找出模塊中的全部策略-另一個種方案
把所有的策略函數(shù),都放到一個模塊(文件)中,然后通過import導(dǎo)入進來,再使用inspect模塊提供的函數(shù)內(nèi)省
示例,獲取一個模塊中的所有函數(shù)
import promotions # 一個包含函數(shù)的模塊 print(inspect.getmembers(promotions, inspect.isfunction)) # 獲取一個模塊中的所有函數(shù)
打印
[('BulkItemPromo', <function BulkItemPromo at 0x0342F2B8>), ('FidelityPromo', <function FidelityPromo at 0x0342F228>), ('LargeOrderPromo', <function LargeOrderPromo at 0x0342F300>)]
示例,內(nèi)省promotions模塊,獲取所有策略函數(shù)
import promotions promotions = [func for func_name, func in inspect.getmembers(promotions, inspect.isfunction)] def best_promotion(order): return max([func(order) for func in promotions])
這樣的話,以后有新的策略,只需要在promotions模塊中新增對應(yīng)的策略函數(shù),就可以自動加載了,妙!
命令模式
命令模式的目的是解耦調(diào)用者(調(diào)用操作的對象)和接收者(提供實現(xiàn)的對象)。讓他們只實現(xiàn)一個方法execute接口,供調(diào)用者使用,這樣調(diào)用者無需了解接受者的接口,而且不同的接受者可以適應(yīng)不同的Command子類。
示例,使用抽象類實現(xiàn)命令模式
import abc class Receiver: """命令的接收者,執(zhí)行命令的地方""" def start(self): print('開始執(zhí)行') def stop(self): print('停止執(zhí)行') class Command(abc.ABC): """命令抽象類""" @abc.abstractmethod def execute(self): """命令對象對外只提供execute方法""" class StartCommand(Command): """開始執(zhí)行的命令""" def __init__(self, receiver): self.receiver = receiver def execute(self): return self.receiver.start() class StopCommand(Command): """停止執(zhí)行的命令""" def __init__(self, receiver): self.receiver = receiver def execute(self): return self.receiver.stop() class Client: """命令的調(diào)用者""" def __init__(self, command): self.command = command def __call__(self, *args, **kwargs): return self.command.execute() start = StartCommand(Receiver()) client = Client(start) client() stop = StopCommand(Receiver()) client = Client(stop) client()
打印
開始執(zhí)行
停止執(zhí)行
可以不使用抽象類,直接使用一個comman()函數(shù)來代理Command示例。使用一等函數(shù)對命令模式重新審視。
到此這篇關(guān)于Python設(shè)計模式中的策略模式詳解的文章就介紹到這了,更多相關(guān)Python策略模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python datetime 和時間戳互相轉(zhuǎn)換問題
time和datetime都是Python中的內(nèi)置模塊(不需要安裝,直接可以使用),都可以對時間進行獲取,對時間格式進行轉(zhuǎn)換,如時間戳和時間字符串的相互轉(zhuǎn)換,本文先給大家介紹python datetime 和時間戳互轉(zhuǎn)問題,感興趣的朋友一起看看吧2022-11-11PyQt通過動畫實現(xiàn)平滑滾動的QScrollArea
這篇文章主要為大家詳細介紹了PyQt如何使用Qt的動畫框架 QPropertyAnimation來實現(xiàn)平滑滾動的QScrollArea,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的可以學(xué)習(xí)一下2023-01-01Python實戰(zhàn)之實現(xiàn)簡單的名片管理系統(tǒng)
這篇文章主要介紹了Python實戰(zhàn)之實現(xiàn)簡單的名片管理系統(tǒng),文中有非常詳細的代碼示例,對正在學(xué)習(xí)python的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04Python+Tableau廣東省人口普查可視化的實現(xiàn)
本文將結(jié)合實例代碼,介紹Python+Tableau廣東省人口普查可視化,第七次人口普查數(shù)據(jù)分析,繪制歷次人口普查人口數(shù)量變化圖,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-06-06聊聊通過celery_one避免Celery定時任務(wù)重復(fù)執(zhí)行的問題
Celery Once 也是利用 Redis 加鎖來實現(xiàn), Celery Once 在 Task 類基礎(chǔ)上實現(xiàn)了 QueueOnce 類,該類提供了任務(wù)去重的功能,今天通過本文給大家介紹通過celery_one避免Celery定時任務(wù)重復(fù)執(zhí)行的問題,感興趣的朋友一起看看吧2021-10-10