Python設(shè)計(jì)模式中的策略模式詳解
策略模式
策略模式是一個(gè)經(jīng)典的模式,簡(jiǎn)化代碼。
電商領(lǐng)域有個(gè)功能明細(xì)可以使用“策略”模式,就是根據(jù)客戶的屬性或訂單中的商品計(jì)算折扣。
比如一個(gè)網(wǎng)店,指定了以下的折扣規(guī)則, 并且一個(gè)訂單只能享受一個(gè)折扣:
- 有1000積分以上的顧客,整個(gè)訂單可以享受5%的折扣
- 同一個(gè)訂單中,單個(gè)商品的數(shù)量達(dá)到20個(gè)以上,單品享受10%折扣
- 訂單中不同商品的數(shù)量達(dá)到10個(gè)以上,整個(gè)訂單享受7%折扣
下面是UML類圖:

上下文:把一些計(jì)算委托給實(shí)現(xiàn)不同算法的可互換組建,他們提供服務(wù)。 在這個(gè)示例中,上下文就是Order,根據(jù)不同算法提供折扣。
策略:實(shí)現(xiàn)不同算法的組件共同接口。在這個(gè)示例中,Promotion的抽象類扮演這個(gè)角色。
具體策略:“策略”的子類,實(shí)現(xiàn)具體策略
示例,實(shí)現(xiàn)Order類,支持插入式折扣策略
import abc
from collections import namedtuple
from abc import abstractmethod
Customer = namedtuple('Customer', 'name fidelity') # name:顧客名字 fidelity:忠誠(chéng)度,這里用積分體現(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) # 購(gòu)物車:商品列表
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ù)點(diǎn),保留2位小數(shù)
class Promotion(abc.ABC):
"""策略:抽象基類"""
@abstractmethod
def discount(self, order):
"""返回折扣金額"""
class FidelityPromo(Promotion):
"""具體策略:有1000積分以上的顧客,整個(gè)訂單可以享受5%的折扣"""
def discount(self, order):
return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
class BulkItemPromo(Promotion):
"""具體策略:同一個(gè)訂單中,單個(gè)商品的數(shù)量達(dá)到20個(gè)以上,單品享受10%折扣"""
def discount(self, order):
# return order.total() * 0.1 if any(item for item in order.cart if item.quantity >= 20) else 0 理解錯(cuò)誤為整體折扣
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
class LargeOrderPromo(Promotion):
"""具體策略:訂單中不同商品的數(shù)量達(dá)到10個(gè)以上,整個(gè)訂單享受7%折扣"""
def discount(self, order):
return order.total() * 0.07 if len({item.product for item in order.cart}) >= 10 else 0
聊一下抽象基類:
Python3.4中,聲明抽象基類的最簡(jiǎn)單的方式是繼承abc.ABC :class Promotion(abc.ABC):
Python3.0到Python3.3中,必須在class中使用metaclass=關(guān)鍵字 : class Promotion(metaclass=abc.ABCMeta):
Python2中,要在 類屬性中增加__metaclass__ = abc.ABCMeta
測(cè)試代碼
# 兩個(gè)顧客,一個(gè)積分0,一個(gè)積分1100
joe = Customer('Joe', 0)
ann = Customer('Ann', 1100)
# 有三個(gè)商品的購(gòu)物車
cart = [LineItem('banana', 4, .5),
LineItem('apple', 10, 1.5),
LineItem('watermelon', 5, 5.0)]
# ann因?yàn)槌^1000積分,獲得了5%折扣
order_joe = Order(joe, cart, FidelityPromo()) #這里要FidelityPromo要加()來創(chuàng)建實(shí)例
print(order_joe)
order_ann = Order(ann, cart, FidelityPromo())
print(order_ann)
# 香蕉30個(gè),蘋果10個(gè)。
banana_cart = [LineItem('banana', 30, .5),
LineItem('apple', 10, 1.5)]
# joe因?yàn)橄憬队?0個(gè),根據(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>
以上的模式中,每個(gè)具體策略都是一個(gè)類,而且只定義了一個(gè)方法:discount。此外他們的策略示例沒有實(shí)例屬性,看起來就像普通函數(shù)。
示例,使用函數(shù)實(shí)現(xiàn)折扣策略
Customer = namedtuple('Customer', 'name fidelity') # name:顧客名字 fidelity:忠誠(chéng)度,這里用積分體現(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) # 購(gòu)物車:商品列表
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ù)點(diǎn),保留2位小數(shù)
def FidelityPromo(order):
"""具體策略:有1000積分以上的顧客,整個(gè)訂單可以享受5%的折扣"""
return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
def BulkItemPromo(order):
"""具體策略:同一個(gè)訂單中,單個(gè)商品的數(shù)量達(dá)到20個(gè)以上,單品享受10%折扣"""
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
def LargeOrderPromo(order):
"""具體策略:訂單中不同商品的數(shù)量達(dá)到10個(gè)以上,整個(gè)訂單享受7%折扣"""
return order.total() * 0.07 if len({item.product for item in order.cart}) >= 10 else 0
# 兩個(gè)顧客,一個(gè)積分0,一個(gè)積分1100
joe = Customer('Joe', 0)
ann = Customer('Ann', 1100)
# 有三個(gè)商品的購(gòu)物車
cart = [LineItem('banana', 4, .5),
LineItem('apple', 10, 1.5),
LineItem('watermelon', 5, 5.0)]
# ann因?yàn)槌^1000積分,獲得了5%折扣
order_joe = Order(joe, cart, FidelityPromo)
print(order_joe)
order_ann = Order(ann, cart, FidelityPromo)
print(order_ann)
# 香蕉30個(gè),蘋果10個(gè)。
banana_cart = [LineItem('banana', 30, .5),
LineItem('apple', 10, 1.5)]
# joe因?yàn)橄憬队?0個(gè),根據(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ù)更加簡(jiǎn)單,代碼量減少。沒必要在新建訂單實(shí)例化新的促銷對(duì)象,函數(shù)拿來即用。
選擇最佳策略
要實(shí)現(xiàn)最佳折扣策略的自動(dòng)選擇,只需要一個(gè)額外的函數(shù)即可。這樣就能自動(dòng)找出最高的折扣。
def best_promotion(order):
promotions = [FidelityPromo, BulkItemPromo, LargeOrderPromo]
return max([func(order) for func in promotions])
自動(dòng)找出模塊中的全部策略
以上的promotions列表包含了三個(gè)策略,當(dāng)再新增新的策略時(shí),需要在這個(gè)列表中追加,使用以下寫法,可以自動(dòng)識(shí)別策略函數(shù):
示例,實(shí)現(xiàn)了把Promo結(jié)尾的函數(shù)引用,放進(jìn)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])
以上實(shí)現(xiàn)的原理就是利用globals()內(nèi)置函數(shù):
globals()返回一個(gè)字典,表示當(dāng)前的全局符號(hào)表。包含了當(dāng)前所有定義的函數(shù)等。
自動(dòng)找出模塊中的全部策略-另一個(gè)種方案
把所有的策略函數(shù),都放到一個(gè)模塊(文件)中,然后通過import導(dǎo)入進(jìn)來,再使用inspect模塊提供的函數(shù)內(nèi)省
示例,獲取一個(gè)模塊中的所有函數(shù)
import promotions # 一個(gè)包含函數(shù)的模塊 print(inspect.getmembers(promotions, inspect.isfunction)) # 獲取一個(gè)模塊中的所有函數(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模塊中新增對(duì)應(yīng)的策略函數(shù),就可以自動(dòng)加載了,妙!
命令模式
命令模式的目的是解耦調(diào)用者(調(diào)用操作的對(duì)象)和接收者(提供實(shí)現(xiàn)的對(duì)象)。讓他們只實(shí)現(xiàn)一個(gè)方法execute接口,供調(diào)用者使用,這樣調(diào)用者無需了解接受者的接口,而且不同的接受者可以適應(yīng)不同的Command子類。
示例,使用抽象類實(shí)現(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):
"""命令對(duì)象對(duì)外只提供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í)行
可以不使用抽象類,直接使用一個(gè)comman()函數(shù)來代理Command示例。使用一等函數(shù)對(duì)命令模式重新審視。
到此這篇關(guān)于Python設(shè)計(jì)模式中的策略模式詳解的文章就介紹到這了,更多相關(guān)Python策略模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python中PyExecJS(執(zhí)行JS代碼庫(kù))的具體使用
pyexecjs是一個(gè)用Python來執(zhí)行JavaScript代碼的工具庫(kù),本文主要介紹了Python中PyExecJS(執(zhí)行JS代碼庫(kù))的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02
Python編程使用PyQt5庫(kù)實(shí)現(xiàn)動(dòng)態(tài)水波進(jìn)度條示例
這篇文章主要介紹了Python編程使用PyQt5庫(kù)實(shí)現(xiàn)動(dòng)態(tài)水波進(jìn)度條的示例代碼解析,有需要的朋友可以借鑒參考下希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2021-10-10
xshell會(huì)話批量遷移到mobaxterm的工具(python小工具)
這篇文章主要介紹了xshell會(huì)話批量遷移到mobaxterm的工具,使用方法也超級(jí)簡(jiǎn)單,本文通過python代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
Python extract及contains方法代碼實(shí)例
這篇文章主要介紹了Python extract及contains方法代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
python學(xué)生信息管理系統(tǒng)實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了python學(xué)生信息管理系統(tǒng)的實(shí)現(xiàn)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
Django 權(quán)限認(rèn)證(根據(jù)不同的用戶,設(shè)置不同的顯示和訪問權(quán)限)
這篇文章主要介紹了Django 權(quán)限認(rèn)證(根據(jù)不同的用戶,設(shè)置不同的顯示和訪問權(quán)限),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
Python中type的構(gòu)造函數(shù)參數(shù)含義說明
這篇文章主要介紹了Python中type的構(gòu)造函數(shù)參數(shù)含義說明,本文用一個(gè)編碼實(shí)例解釋Python type的參數(shù)的作用和含義,需要的朋友可以參考下2015-06-06
Python標(biāo)準(zhǔn)庫(kù)之collections包的使用教程
這篇文章主要給大家介紹了Python標(biāo)準(zhǔn)庫(kù)之collections包的使用教程,詳細(xì)介紹了collections中多個(gè)集合類的使用方法,相信對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面隨小編一起來學(xué)習(xí)學(xué)習(xí)吧。2017-04-04

