策略模式

267 阅读5分钟

在《设计模式》这本书中对策略模式的概述如下:

定义一系列算法,把它们一一封装起来,并且使它们可以相互替换。本模式使得算法可以独立于使用它的客户而变化。

如果通过概括来看,策略模式还是显得极其晦涩的,如果通过实例则可以更好的理解所谓的策略模式

理发店迎新活动:
(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: 通过图表展示,不同理发次数下各项策略的价格

理发办卡.png

完整版代码如下:

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)

策略模式的优势和缺点

策略模式.png 引用自知乎