python设计模式之策略模式
每次看到项目中存在大量的if else代码时,都会心生一丝不安全感。 特别是产品给的需求需要添加或者更改一种if条件时,生怕会因为自己的疏忽而使代码天崩地裂,哈哈,本文的目的就是来解决这种不安全感的,23种设计模式的策略模式。
GOF对策略模式的解释是: 定义一系列算法, 把它们一个个封装起来,并且使它们可相互替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展, 子类化)。
策略模式结构图如下:
生活中商场搞活动给消费者派送了一批优惠券,优惠券分三种, 一种是满减券满100减10元, 一种是打折券满200打8折, 一种折扣券是满300打7.5折然后送鸡蛋一打(价值15),如果我们是商场后台程序员,我们要计算每个客户最后实际支付了多少钱,那这个程序你会怎样去设计?
01、 没有使用设计模式的版本
class Order: def __init__(self, money, coupon): self.money = money self.coupon = coupon def total_money(self): if self.coupon == 1 and self.money >= 100: return self.money - 10 elif self.coupon == 2 and self.money >= 200: return self.money * 0.8 elif self.coupon == 3 and self.money >= 300: return self.money * 0.75 -15 return self.money if __name__ == '__main__': order1 = Order(102, 1) order2 = Order(250, 2) order3 = Order(372, 3) order4 = Order(190, 2) o1 = order1.total_money() o2 = order2.total_money() o3 = order3.total_money() o4 = order4.total_money() print(o1, o2, o3, o4)
在平常的开发过程中,我们及其容易设计出这样的代码,夹杂着大量的if else语句,程序耦合性很高,一旦发生修改不小心就天崩地裂了, 严重违反我们八大设计原则的开闭原则。
接下来我们用策略设计模式来设计我们的程序。
02、首先设计策略基类
from abc import ABC, abstractmethod class BaseStrategy(ABC): @abstractmethod def calculate(cls, money): pass
策略基类继承ABC类(抽象类)在方法上加上abstractmethod表示子类必须重写这个方法,不然会报错。
03、设计策略子类
class FullOneHundredStrategy(BaseStrategy): @classmethod def calculate(cls, money): if money >= 100: return money - 10 return money class FullTwoHundredStrategy(BaseStrategy): @classmethod def calculate(cls, money): if money >= 200: return money * 0.8 return money class FullThreeHundredStrategy(BaseStrategy): @classmethod def calculate(cls, money): if money >= 300: return money * 0.75 - 15 return money
分别设计三个策略子类并重写对应的计算方法,如果以后新增新的优惠券就可以增加一个新的子类即可。
04、设计订单类
class Order: coupon_map = { 1: FullOneHundredStrategy, 2: FullTwoHundredStrategy, 3: FullThreeHundredStrategy } def __init__(self, money, coupon): self.money = money self.coupon = coupon def total_money(self): return self.coupon_map.get(self.coupon).calculate(self.money)
订单类包括实例属性money,和优惠券的种类, 并有一个类属性map,在实际开发中这个Map一般会放到公共文件中。
05、上下文中计算总金额
if __name__ == '__main__': order1 = Order(102, 1) order2 = Order(250, 2) order3 = Order(372, 3) order4 = Order(190, 2) o1 = order1.total_money() o2 = order2.total_money() o3 = order3.total_money() o4 = order4.total_money() print(o1, o2, o3, o4)
我们打印结果:
和我们期望的结果一样,这样就完美的消除了大量的if else语句了, 更重要的是这样使策略与订单类解耦了,增加了程序的可复用性,和维护性。
06、总结
策略设计模式的好处:
- Strategy及其子类为组件提供了一系列可重用的算法, 从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。
- Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常需要Strategy模式。
策略模式的劣处:
- 当策略越来愈多,就需要写更多的策略子类,后期接手的人可能就会有点懵了。
- 消除的if else判断语句仅适用于前后判断之间没有强关联性的情况, 如果判断有强关联可能需要用到另一个设计模式责任链设计模式。
ps:前后判断强关联如上述举例改成:商场活动不需要优惠券,只要你消费满100就减10,满200打8折,这样前后判断就有比较强的关联性了,此时的策略模式就不能有效消除if else判断语句了。
在最后我们依然需要加上我们设计模式的八大原则:
- 依赖倒置原则(DIP)
- 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定) 。
- 抽象(稳定)不应该依赖于实现细节(变化) ,实现细节应该依赖于抽象(稳定)。
- 开放封闭原则(OCP)
- 对扩展开放,对更改封闭。
- 类模块应该是可扩展的,但是不可修改。
- 单一职责原则(SRP)
- 一个类应该仅有一个引起它变化的原因。
- 变化的方向隐含着类的责任。
- Liskov 替换原则(LSP)
- 子类必须能够替换它们的基类(IS-A)。
- 继承表达类型抽象。
- 接口隔离原则(ISP)
- 不应该强迫客户程序依赖它们不用的方法。
- 接口应该小而完备。
- 优先使用对象组合,而不是类继承
- 类继承通常为“白箱复用”,对象组合通常为“黑箱复用” 。
- 继承在某种程度上破坏了封装性,子类父类耦合度高。
- 而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
- 封装变化点
- 使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。
- 针对接口编程,而不是针对实现编程
- 不将变量类型声明为某个特定的具体类,而是声明为某个接口。
- 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。
- 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案