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

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

比如,超市做活动,如果你的购物积分满1000,就可以按兑换现金抵用券10元,如果购买同一商品满10件,就可以打9折,如果如果购买的金额超过500,就可以享受满减50元的优惠。这是三个不同的促销策略。

再比如,联系朋友、同学,可以打电话,也可以发短信,可以发微信,也可以发邮件,这是四个不同的联系策略。

再比如,去外出旅游,我们可以选择火车,也可以选择公共汽车,可以选择飞机,也可以选择自驾游。这又是四个不同的出行策略。

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

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

  • 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》


原文地址:https://blog.51cto.com/14237772/2382960

时间: 2024-07-31 10:19:26

【经典案例】Python详解设计模式:策略模式的相关文章

设计模式 - 策略模式(Strategy Pattern) 详解

策略模式(Strategy Pattern) 详解 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26577879 本文版权所有, 禁止转载, 如有需要, 请站内联系. 策略模式: 定义了算法族, 分别封装起来, 让它们之间可以相互替换, 此模式让算法的变化独立于使用算法的客户. 对于父类的子类族需要经常扩展新的功能, 为了使用父类比较灵活的添加子类, 把父类的行为写成接口(interface)的形式; 使用set()方法,

华为交换机私有hybird接口模式:(案例+原理详解)

华为交换机私有hybird接口模式:(案例+原理详解) 实验说明: 准备:如图pc1 pc2同属于VLAN10,配置相应的ippc3 pc4同属于VLAN20 配置相应的ipClient 属于 VLAN30 配置pc1同网段ipPc1 pc2 client 属于同网段但是不同VLAN 交换机分别新建VLAN 10 20 30 目的:实现VLAN间相互通信,VLAN10 VLAN20不能相互访问,VLAN10 VLAN30可以相互访问.过程步骤: 实现同VLAN间通信:a) Pc1数据收发过程:p

java设计模式-策略模式

定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它的客户而独立变化. 认识策略模式 策略模式的重心 策略模式的重心不是如何实现算法,而是如何组织.调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性. 算法的平等性 策略模式一个很大的特点就是各个策略算法的平等性.对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换.所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的. 所以可以

python 详解re模块(转载)

正则表达式的元字符有. ^ $ * ? { [ ] | ( ).表示任意字符[]用来匹配一个指定的字符类别,所谓的字符类别就是你想匹配的一个字符集,对于字符集中的字符可以理解成或的关系.^ 如果放在字符串的开头,则表示取非的意思.[^5]表示除了5之外的其他字符.而如果^不在字符串的开头,则表示它本身. 具有重复功能的元字符:* 对于前一个字符重复0到无穷次对于前一个字符重复1到无穷次?对于前一个字符重复0到1次{m,n} 对于前一个字符重复次数在为m到n次,其中,{0,} = *,{1,} =

经典栈溢出利用详解一例—Notepad++插件CCompletion

标 题: 经典栈溢出利用详解一例-Notepad++插件CCompletion 时 间: 2014-02-23,21:08:51 回顾 上篇文章介绍了Noetpad++程序中的一个插件CCompletion存在的一个因使用不安全的lstrcpyW函数拷贝字符串造成的栈溢出漏洞,并且确定了漏洞的大致利用入口,已经找到了可控EIP数据在整个输入数据中的精确位置,但是如果要写出可以利用的Shell Code还需是需要费一番功夫去调试和修正的.这篇文章就按照前面所说的那个漏洞的利用入口来详细的介绍一个可

设计模式 - 策略模式(Strategy Pattern) 具体解释

策略模式(Strategy Pattern) 具体解释 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26577879 本文版权全部, 禁止转载, 如有须要, 请站内联系. 策略模式: 定义了算法族, 分别封装起来, 让它们之间能够相互替换, 此模式让算法的变化独立于使用算法的客户. 对于父类的子类族须要常常扩展新的功能, 为了使用父类比較灵活的加入子类, 把父类的行为写成接口(interface)的形式; 使用set()方法

详解设计模式六大原则

设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的:设计模式使代码编制真正工程化:设计模式是软件工程的基石脉络,如同大厦的结构一样. 借用并改编一下鲁迅老师<故乡>中的一句话,一句话概括设计模式: 希望本无所谓有,无所谓无.这正如coding的设计模式,其实coding本没有设计模式,用的人多了,也便成了设计模式 v六大原

2.大话设计模式-策略模式

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace DesignModel 8 { 9 /// <summary> 10 /// 策略模式 11 /// </summary> 12 public class TacticsModel 13 { 14 //对于

(5.2)uboot详解——省电模式(番外)

(5.2)uboot详解--省电模式(番外) 这篇文章将对uboot的省电模式进行分析,这里介绍的内容与uboot的启动其实关系不大,如果关心uboot的启动过程,可以跳过这节以及后面的小节,直接到第6章. 省电模式和cpu的工作模式(异常)其实关系也不大,省电模式主要是依靠时钟来分类的,因为外设的工作必须要时钟,当停止给外设提供时钟的时候,相应的外设也会停止工作,所以省电管理就是根据控制是否给相应的设备提供时钟或电源来达到节电的目的. ARM有四中节电模式: 普通模式:这种模式下,会给所有的外