在《设计模式》这本书中对策略模式的概述如下:
定义一系列算法,把它们一一封装起来,并且使它们可以相互替换。本模式使得算法可以独立于使用它的客户而变化。
如果通过概括来看,策略模式还是显得极其晦涩的,如果通过实例则可以更好的理解所谓的策略模式
理发店迎新活动:
(1) 每次理发消费20元
(2) 套餐一: 充值100元送50元,每次理发打九折
(3) 套餐二: 充值200元送100元,每次理发打八折
(4) 办卡后无法办理退费服务: 这里是为了让项目简单一点
根据不同的客户需求为客户推荐不同的方案
用户类设计
from typing import NamedTuple
class User(NamedTuple):
name:str # 客户名称
num:int # 预估在一定区间内的理发次数
基于NameTuple设计一个用户类,来存储不同的用户信息,其中包含用户的名字和用户在一定周期(一年、两年)的预估理发次数
设计上下文
class Order(NamedTuple):
user:User # 用户信息
aMoney:int # 理发不打折的价格
promotion:Promotion = None # 用户的策略选择,默认是不办卡
def total(self) -> float:
"""用来计算客户的一个消费数值"""
if not self.promotion:
money = self.user.num * self.aMoney
else:
money = self.promotion(self)
return money
def __repr__(self):
return f"{self.user.name}理发{self.user.num}次,共计消费{self.total()}"
提供一个服务,把一些计算委托给实现不同算法的可互换组件(上下文)
设计策略接口
class Promotion(ABC):
@abstractmethod
def discount(self, order:Order):
"""用来计算客户理发的消费情况"""
继承自ABC,且函数通过abstractmethod装饰的,称为接口,子类必须重写它,否则子类无法实例化
设计不同的子类策略
class ActivityOne(Promotion):
"""充值100元送50元,每次理发打九折"""
account = 150 # 卡里初始150元
money = 100 # 充值就要消费100元,且无法退款
def discount(self, order:Order):
consume = order.user.num * (order.aMoney * 0.9) # 计算理发消费
if consume <= self.account: # 如果卡里还有钱
return self.money
else:
return self.money + (consume - self.account) # (consume - self.account) 卡里钱被消费完,在充值的费用
class ActivityTwo(Promotion):
"""充值200元送100元,每次理发打八折"""
account = 300 # 卡里初始300元
money = 200 # 充值就要消费200元,且无法退款
def discount(self, order:Order):
consume = order.user.num * (order.aMoney * 0.8) # 计算理发消费
if consume <= self.account: # 如果卡里还有钱
return self.money
else:
return self.money + (consume - self.account) # (consume - self.account) 卡里钱被消费完,在充值的费用
完整代码
from typing import NamedTuple
from abc import ABC, abstractmethod
class User(NamedTuple):
name:str # 客户名称
num:int # 预估在一定区间内的理发次数
class Promotion(ABC):
@abstractmethod
def discount(self, order):
"""用来计算客户理发的消费情况"""
class Order(NamedTuple):
user:User # 用户信息
aMoney:int # 理发不打折的价格
promotion:Promotion = None # 用户的策略选择,默认是不办卡
def total(self) -> float:
"""用来计算客户的一个消费数值"""
if not self.promotion:
money = self.user.num * self.aMoney
else:
money = self.promotion.discount(self)
return money
def __repr__(self):
return f"{self.user.name}理发{self.user.num}次,共计消费{self.total()}"
class ActivityOne(Promotion):
"""充值100元送50元,每次理发打九折"""
account = 150 # 卡里初始150元
money = 100 # 充值就要消费100元,且无法退款
def discount(self, order:Order):
consume = order.user.num * (order.aMoney * 0.9) # 计算理发消费
if consume <= self.account: # 如果卡里还有钱
return self.money
else:
return self.money + (consume - self.account) # (consume - self.account) 卡里钱被消费完,在充值的费用
class ActivityTwo(Promotion):
"""充值200元送100元,每次理发打八折"""
account = 300 # 卡里初始300元
money = 200 # 充值就要消费200元,且无法退款
def discount(self, order:Order):
consume = order.user.num * (order.aMoney * 0.8) # 计算理发消费
if consume <= self.account: # 如果卡里还有钱
return self.money
else:
return self.money + (consume - self.account) # (consume - self.account) 卡里钱被消费完,在充值的费用
if __name__ == "__main__":
user = User("童话", 5)
order1 = Order(user, 20)
order2 = Order(user, 20, ActivityOne())
order3 = Order(user, 20, ActivityTwo())
print(order1)
print(order2)
print(order3)
补充1: 通过图表展示,不同理发次数下各项策略的价格
完整版代码如下:
from typing import NamedTuple
from abc import ABC, abstractmethod
class User(NamedTuple):
name:str # 客户名称
num:int # 预估在一定区间内的理发次数
class Promotion(ABC):
@abstractmethod
def discount(self, order):
"""用来计算客户理发的消费情况"""
class Order(NamedTuple):
user:User # 用户信息
aMoney:int # 理发不打折的价格
promotion:Promotion = None # 用户的策略选择,默认是不办卡
def total(self) -> float:
"""用来计算客户的一个消费数值"""
if not self.promotion:
money = self.user.num * self.aMoney
else:
money = self.promotion.discount(self)
return money
def __repr__(self):
return f"{self.user.name}理发{self.user.num}次,共计消费{self.total()}"
class ActivityOne(Promotion):
"""充值100元送50元,每次理发打九折"""
account = 150 # 卡里初始150元
money = 100 # 充值就要消费100元,且无法退款
def discount(self, order:Order):
consume = order.user.num * (order.aMoney * 0.9) # 计算理发消费
if consume <= self.account: # 如果卡里还有钱
return self.money
else:
return self.money + (consume - self.account) # (consume - self.account) 卡里钱被消费完,在充值的费用
class ActivityTwo(Promotion):
"""充值200元送100元,每次理发打八折"""
account = 300 # 卡里初始300元
money = 200 # 充值就要消费200元,且无法退款
def discount(self, order:Order):
consume = order.user.num * (order.aMoney * 0.8) # 计算理发消费
if consume <= self.account: # 如果卡里还有钱
return self.money
else:
return self.money + (consume - self.account) # (consume - self.account) 卡里钱被消费完,在充值的费用
def print_table(data, headers):
if headers:
data.insert(0, headers)
# 获取每一列的最大宽度
col_widths = [max(len(str(item)) for item in col) for col in zip(*data)]
def print_row(rows):
print("| " + " | ".join(str(item).ljust(width) for item, width in zip(rows, col_widths)) + " |")
# 打印表格
print_row(data[0])
print("+-" + "-+-".join('-' * width for width in col_widths) + "-+")
for row in data[1:]:
print_row(row)
def ShowTable(start:int, end:int):
Plans = [
{"plan": "不办卡", "value": None},
{"plan": "套餐1", "value": ActivityOne()},
{"plan": "套餐2", "value": ActivityTwo()}
]
datas = []
for i in range(start, end + 1):
user = User("张三", i)
lis = [i]
lis.extend([Order(user, 20, dic['value']).total() for dic in Plans])
datas.append(lis)
print_table(datas, headers=["理发次数"]+[dic['plan'] for dic in Plans])
if __name__ == "__main__":
ShowTable(3, 20)