【经典案例】Python详解设计模式:策略模式

完成一项任务每每有多种方式,咱们将其称之为策略。python

好比,超市作活动,若是你的购物积分满1000,就能够按兑换现金抵用券10元,若是购买同一商品满10件,就能够打9折,若是若是购买的金额超过500,就能够享受满减50元的优惠。这是三个不一样的促销策略。设计模式

再好比,联系朋友、同窗,能够打电话,也能够发短信,能够发微信,也能够发邮件,这是四个不一样的联系策略。微信

再好比,去外出旅游,咱们能够选择火车,也能够选择公共汽车,能够选择飞机,也能够选择自驾游。这又是四个不一样的出行策略。app

以上这些真实场景,都有策略选择模型的影子,能够考虑使用策略模式。ide

经典的策略模式,是由三部分组成函数

  • Context:上下文环境类
  • Stragety:策略基类
  • ConcreteStragety:具体策略

以第一个超市作活动的场景来举个例子。学习

  • Context:Order类,订单信息,包括商品,价格和数量,觉得购买者等
  • Stragety:Promotion类,抽象基类,包含一个抽象方法(计算折扣)
  • ContreteStragety:分三个类,FidelityPromo,BulkItemPromo,LargeOrderPromo,实现具体的折扣计算方法。

首先是 Order 类:优化

class Item:
    def __init__(self, issue, price, quantity):
        self.issue = issue
        self.price = price
        self.quantity = quantity

    def total(self):
        return self.price * self.quantity

class Order:
    def __init__(self, customer, promotion=None):
        self.cart = []
        self.customer = customer
        self.promotion = promotion

    def add_to_cart(self, *items):
        for item in items:
            self.cart.append(item)

    def total(self):
        total = 0
        for item in self.cart:
            total += item.total()

        return total

    def due(self):
        if not self.promotion:
            discount = 0
        else:
            discount  = self.promotion.discount(self)
        return (self.total() - discount)

而后是积分兑换现金券的策略,为了保证咱们的代码具备良好的可扩展性及维护性,我会先写一个策略类,它是一个抽象基类,它的子类都是一个具体的策略,都必须实现 discount 方法,就好比我们的积分兑换现金策略。编码

from abc import ABC, abstractmethod

class Promotion(ABC):
    @abstractmethod
    def discount(self, order):
        pass


class FidelityPromo(Promotion):
    '''
    若是积分满1000分,就能够兑换10元现金券
    '''
    def discount(self, order):
        return 10 if order.customer.fidelity >1000 else 0

假设如今小明去商场买了一件衣服(600块),两双鞋子(200*2),他的购物积分有1500点。插件

在平时,商场通常都没有活动,可是终年都有积分换现金抵用券的活动。

>>> from collections import namedtuple

# 定义两个字段:名字,购物积分
>>> Customer = namedtuple('Customer', 'name fidelity')
>>> xm = Customer('小明', 1500)
>>> item1 = Item('鞋子', 200, 3)
>>> item2 = Item('衣服', 600, 1)
>>> order = Order(xm, FidelityPromo())
>>> order.add_to_cart(item1, item2)

# 原价 1200,用上积分后,只要1190
>>> order
<Order Total:1200 due:1190>

眼看着,五一节也快了,商场准备大搞促销

  • 只要单项商品购买10件,便可9折。
  • 若是订单总金额大于等于500,就能够立减50。

有了此前咱们使用 策略模式 打下的基础,咱们并非使用硬编码的方式来配置策略,因此不须要改动太多的源码,只要直接定义五一节的两个促销策略类便可(一样继承自 Promotion 抽象基类),就像插件同样,即插即用。

class BulkItemPromo(Promotion):
    '''
    若是单项商品购买10件,便可9折。
    '''
    def discount(self, order):
        discount = 0
        for item in order.cart:
            if item.quantity >= 10:
                discount += item.total() * 0.1
        return discount

class LargeOrderPromo(Promotion):
    '''
    若是订单总金额大于等于500,就能够立减50
    '''
    def discount(self, order):
        discount = 0
        if order.total() >= 500:
            discount = 50

        return discount

看到商场活动如此给力,小明的钱包也鼓了起来,开始屯起了生活用品。

若是使用了第一个策略,原价600,只须要花 580

>>> from collections import namedtuple
>>> Customer = namedtuple('Customer', 'name fidelity')

>>> xm = Customer('小明', 300)

>>> item1 = Item('纸巾', 20, 10)
>>> item2 = Item('食用油', 50, 4)
>>> item3 = Item('牛奶', 50, 4)


>>> order = Order(xm, BulkItemPromo())
>>> order.add_to_cart(item1, item2, item3)

>>> order
<Order Total:600 due:580.0>

若是使用了第二个策略,原价600,只须要花550

>>> from collections import namedtuple
>>> Customer = namedtuple('Customer', 'name fidelity')

>>> xm = Customer('小明', 300)

>>> item1 = Item('纸巾', 20, 10)
>>> item2 = Item('食用油', 50, 4)
>>> item3 = Item('牛奶', 50, 4)


>>> order = Order(xm, LargeOrderPromo())
>>> order.add_to_cart(item1, item2, item3)

>>> order
<Order Total:600 due:550>

两个策略即插即用,只须要在前台下订单时,选择对应的策略便可,原业务逻辑无需改动。

>>> order = Order(xm, BulkItemPromo())
>>> order = Order(xm, LargeOrderPromo())

可是问题很快又来了,商场搞活动,却让顾客手动选择使用哪一个优惠策略,做为一个良心的商家,应该要能自动对比全部策略得出最优惠的价格来给到顾客。这就要求后台代码要可以找出当前可用的所有策略,并一一比对折扣。

# 找出全部的促销策略
all_promotion = [globals()[name] for name in globals() if name.endswith('Promo') and name != 'BestPromo']

# 实现一个最优策略类
class BestPromo(Promotion):
    def discount(self, order):
        # 找出当前文件中全部的策略
        all_promotion = [globals()[name] for name in globals() if name.endswith('Promo') and name != 'BestPromo']

        # 计算最大折扣
        return max([promo().discount(order) for promo in all_promotion])

在前台下订单的时候,就会自动计算全部的优惠策略,直接告诉顾客最便宜的价格。

# 直接选择这个最优策略
>>> order = Order(xm, BestPromo())
>>> order.add_to_cart(item1, item2, item3)

>>> order
<Order Total:600 due:550>

经过以上例子,能够总结出使用策略模式的好处

  1. 扩展性优秀,移植方便,使用灵活。能够很方便扩展策略;
  2. 各个策略能够自由切换。这也是依赖抽象类设计接口的好处之一;

但同时,策略模式 也会带来一些弊端。

  1. 项目比较庞大时,策略可能比较多,不便于维护;
  2. 策略的使用方必须知道有哪些策略,才能决定使用哪个策略,这与迪米特法则是相违背的。

对于以上的例子,仔细一想,其实还有很多能够优化的地方。

好比,为了实现经典的模式,咱们先要定义一个抽象基类,再实现具体的策略类。对于上面这样一个简单的计算折扣价格逻辑来讲,其实能够用函数来实现,而后在实例化 Order 类时指定这个策略函数便可,大可没必要将类给搬出来。这样就能够避免在下订单时,不断的建立策略对象,减小多余的运行时消耗。这里就不具体写出代码了。

因此学习设计模式,不只要知道如何利用这样的模式组织代码,更要领会其思想,活学活用,灵活变通。

以上,就是今天关于 策略模式 的一些我的分享,若有讲得不到位的,还请后台留言指正!

参考文档

  • 《流畅的Python》

关注公众号,获取最新干货!

相关文章
相关标签/搜索