網站首頁 編程語言 正文
策略模式
策略模式是一個經典的模式,簡化代碼。
電商領域有個功能明細可以使用“策略”模式,就是根據客戶的屬性或訂單中的商品計算折扣。
比如一個網店,指定了以下的折扣規則, 并且一個訂單只能享受一個折扣:
- 有1000積分以上的顧客,整個訂單可以享受5%的折扣
- 同一個訂單中,單個商品的數量達到20個以上,單品享受10%折扣
- 訂單中不同商品的數量達到10個以上,整個訂單享受7%折扣
下面是UML類圖:
上下文:把一些計算委托給實現不同算法的可互換組建,他們提供服務。 在這個示例中,上下文就是Order,根據不同算法提供折扣。
策略:實現不同算法的組件共同接口。在這個示例中,Promotion的抽象類扮演這個角色。
具體策略:“策略”的子類,實現具體策略
示例,實現Order類,支持插入式折扣策略
import abc
from collections import namedtuple
from abc import abstractmethod
Customer = namedtuple('Customer', 'name fidelity') # name:顧客名字 fidelity:忠誠度,這里用積分體現
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}表示輸出小數點,保留2位小數
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):
"""具體策略:同一個訂單中,單個商品的數量達到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):
"""具體策略:訂單中不同商品的數量達到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=關鍵字 : 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要加()來創建實例
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個,根據BulkItemPromo 香蕉優惠了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。此外他們的策略示例沒有實例屬性,看起來就像普通函數。
示例,使用函數實現折扣策略
Customer = namedtuple('Customer', 'name fidelity') # name:顧客名字 fidelity:忠誠度,這里用積分體現
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}表示輸出小數點,保留2位小數
def FidelityPromo(order):
"""具體策略:有1000積分以上的顧客,整個訂單可以享受5%的折扣"""
return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
def BulkItemPromo(order):
"""具體策略:同一個訂單中,單個商品的數量達到20個以上,單品享受10%折扣"""
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
def LargeOrderPromo(order):
"""具體策略:訂單中不同商品的數量達到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個,根據BulkItemPromo 香蕉優惠了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>
以上可以看到,使用函數更加簡單,代碼量減少。沒必要在新建訂單實例化新的促銷對象,函數拿來即用。
選擇最佳策略
要實現最佳折扣策略的自動選擇,只需要一個額外的函數即可。這樣就能自動找出最高的折扣。
def best_promotion(order):
promotions = [FidelityPromo, BulkItemPromo, LargeOrderPromo]
return max([func(order) for func in promotions])
自動找出模塊中的全部策略
以上的promotions列表包含了三個策略,當再新增新的策略時,需要在這個列表中追加,使用以下寫法,可以自動識別策略函數:
示例,實現了把Promo結尾的函數引用,放進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])
以上實現的原理就是利用globals()內置函數:
globals()返回一個字典,表示當前的全局符號表。包含了當前所有定義的函數等。
自動找出模塊中的全部策略-另一個種方案
把所有的策略函數,都放到一個模塊(文件)中,然后通過import導入進來,再使用inspect模塊提供的函數內省
示例,獲取一個模塊中的所有函數
import promotions # 一個包含函數的模塊
print(inspect.getmembers(promotions, inspect.isfunction)) # 獲取一個模塊中的所有函數
打印
[('BulkItemPromo', <function BulkItemPromo at 0x0342F2B8>), ('FidelityPromo', <function FidelityPromo at 0x0342F228>), ('LargeOrderPromo', <function LargeOrderPromo at 0x0342F300>)]
示例,內省promotions模塊,獲取所有策略函數
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模塊中新增對應的策略函數,就可以自動加載了,妙!
命令模式
命令模式的目的是解耦調用者(調用操作的對象)和接收者(提供實現的對象)。讓他們只實現一個方法execute接口,供調用者使用,這樣調用者無需了解接受者的接口,而且不同的接受者可以適應不同的Command子類。
示例,使用抽象類實現命令模式
import abc
class Receiver:
"""命令的接收者,執行命令的地方"""
def start(self):
print('開始執行')
def stop(self):
print('停止執行')
class Command(abc.ABC):
"""命令抽象類"""
@abc.abstractmethod
def execute(self):
"""命令對象對外只提供execute方法"""
class StartCommand(Command):
"""開始執行的命令"""
def __init__(self, receiver):
self.receiver = receiver
def execute(self):
return self.receiver.start()
class StopCommand(Command):
"""停止執行的命令"""
def __init__(self, receiver):
self.receiver = receiver
def execute(self):
return self.receiver.stop()
class Client:
"""命令的調用者"""
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()
打印
開始執行
停止執行
可以不使用抽象類,直接使用一個comman()函數來代理Command示例。使用一等函數對命令模式重新審視。
原文鏈接:https://blog.csdn.net/lijiachang8/article/details/124211725
相關推薦
- 2023-03-23 Python中win32com模塊的使用_python
- 2022-11-18 詳解Rust中三種循環(loop,while,for)的使用_Rust語言
- 2022-10-14 wget -c 斷點續傳命令
- 2022-11-01 Python正則表達中re模塊的使用_python
- 2022-07-25 軟件測試業務梳理的實用技巧_應用技巧
- 2022-10-30 Android?動態加載?so實現示例詳解_Android
- 2022-07-17 SQL?Server中使用表變量和臨時表_MsSql
- 2022-06-04 Python學習之魔法函數(filter,map,reduce)詳解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支