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

比如,超市做活动,如果你的购物积分满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》

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

  1. javascript设计模式详解之策略模式

    接上篇命令模式来继续看下js设计模式中另一种常用的模式,策略模式.策略模式也是js开发中常用的一种实例,不要被这么略显深邃的名字给迷惑了.接下来我们慢慢看一下. 一.基本概念与使用场景: 基本概念:定 ...

  2. javascript设计模式详解之命令模式

    每种设计模式的出现都是为了弥补语言在某方面的不足,解决特定环境下的问题.思想是相通的.只不过不同的设计语言有其特定的实现.对javascript这种动态语言来说,弱类型的特性,与生俱来的多态性,导致某 ...

  3. GOF提出的23种设计模式是哪些 设计模式有创建形、行为形、结构形三种类别 常用的Javascript中常用设计模式的其中17种 详解设计模式六大原则

    20151218mark 延伸扩展: -设计模式在很多语言PHP.JAVA.C#.C++.JS等都有各自的使用,但原理是相同的,比如JS常用的Javascript设计模式 -详解设计模式六大原则 设计 ...

  4. [.net 面向对象程序设计深入](24)实战设计模式——策略模式(行为型)

    [.net 面向对象程序设计深入](24)实战设计模式——策略模式(行为型) 1,策略模式定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它 ...

  5. [转载]python 详解re模块

    原文地址:python 详解re模块作者:Rocky 正则表达式的元字符有. ^ $ * ? { [ ] | ( ) .表示任意字符 []用来匹配一个指定的字符类别,所谓的字符类别就是你想匹配的一个字 ...

  6. [Spark内核] 第36课:TaskScheduler内幕天机解密:Spark shell案例运行日志详解、TaskScheduler和SchedulerBackend、FIFO与FAIR、Task运行时本地性算法详解等

    本課主題 通过 Spark-shell 窥探程序运行时的状况 TaskScheduler 与 SchedulerBackend 之间的关系 FIFO 与 FAIR 两种调度模式彻底解密 Task 数据 ...

  7. [.net 面向对象程序设计深入](26)实战设计模式——策略模式 Strategy (行为型)

    [.net 面向对象程序设计深入](26)实战设计模式——策略模式 Strategy (行为型) 1,策略模式定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模 ...

  8. 33 Python 详解命令解析 - argparse--更加详细--转载

    https://blog.csdn.net/lis_12/article/details/54618868 Python 详解命令行解析 - argparse Python 详解命令行解析 - arg ...

  9. 15. 星际争霸之php设计模式--策略模式

    题记==============================================================================本php设计模式专辑来源于博客(jymo ...

随机推荐

  1. privoxy自动请求转发到多个网络

    有些时候我们需要通过不同的代理访问不同资源,比如某些ip或域名走本地网络,某些ip或域名走不可描述的代理等.当然这只是举个栗子! 我要解决的问题是:我的内网机器没有internet访问权限,但是我的应 ...

  2. 信息论随笔3: 交叉熵与TF-IDF模型

    接上文:信息论随笔2: 交叉熵.相对熵,及上上文:信息论随笔 在读<数学之美>的时候,相关性那一节对TF-IDF模型有这样一句描述:"其实 IDF 的概念就是一个特定条件下.关键 ...

  3. 华盛顿邮报:FBI 屡次夸大了“手机加密威胁”的数字

    <华盛顿邮报>周二报道称,美国联邦调查局(FBI)严重夸大了由加密手机所造成的问题.以去年为例,该机构调查人员声称被大约 7800 部涉嫌犯罪活动的加密设备挡在了门外,而准确的数字应该在 ...

  4. eclipse中去掉py文件中烦人的黄色弹框

    eclipse中写py文件,当鼠标点击在参数上时总是出现黄线的弹框,影响人操作,感觉特别烦,如下: 解决方案: windows--preferences--hover--pydev--hover取消选 ...

  5. Go语言中的面向对象

    前言 如果说最纯粹的面向对象语言,我觉得是Java无疑.而且Java语言的面向对象也是很直观,很容易理解的.class是基础,其他都是要写在class里的. 最近学习了Go语言,有了一些对比和思考.虽 ...

  6. formidable处理node.js的post请求

    前言 我们都知道在node.js中,我们最常用的请求方式是get和post.其中get请求和URL相关,通过解析URL我们可以直接获取到请求的参数.但是post请求不同,post请求是包含在请求体中, ...

  7. Sharepoint模态窗体(实战)

    分享人:广州华软 无名 一. 前言 对SharePoint二次开发时,需要知道SharePoint有什么.没有什么,才能在开发过程中避免重复造轮子.SharePoint提供了许多开箱即用的功能,这次要 ...

  8. 删除表中多余的重复记录,重复记录是根据单个字段(Id)来判断,只留有rowid最小的记录

    delete from Resource where Title in (select Title from Resource group by Title having count(Title) & ...

  9. 如何用人工的方式将Excel里的一堆数字变成一个数组

    目的是抛砖引玉,有谁可以教教我如何吧Excle的数据导入MyEclipse么? 如果只有⑨个字符的话我肯定是直接人工输入的,然而这次有65536行乘以3组,遭不住啊. 一.数组之间要有逗号在B列右键, ...

  10. Hadoop系列007-HDFS客户端操作

    title: Hadoop系列007-HDFS客户端操作 date: 2018-12-6 15:52:55 updated: 2018-12-6 15:52:55 categories: Hadoop ...