策略模式
模式定义
策略模式将算法独立成一个类层次,允许它们之间可以相互替换,这些替换甚至可以发生在运行时。该模式让算法的变化独立于使用算法的客户。
策略模式在GOF95中分类为行为模式。
模式结构与说明
- Strategy:策略接口,用来封装算法类层次
- ConcreteStrategy:具体的策略实现,即具体算法,这些实现的地位是平等的,因而可以相互替换
- Context:上下文,负责和具体的策略交互,持有一个具体的策略实现。上下文可能调用策略实现,以实现算法逻辑
- 某些情况下,策略实现可能需要获知上下文的信息才能完成逻辑,此时,可以将上下文作为构造参数传递给策略实现
策略模式很好的体现了开闭原则、里氏替换原则,在以下应用场景下可以选择该模式:
- 有许多相关的类,它们仅仅是行为逻辑有差别时
- 出现同一个算法,有很多不同实现方式时
- 需要封装的算法,有一些与算法本身相关的数据结构时,可以避免暴露这些结构
- 通过分支结构选择算法时
应用举例
策略模式经典的例子是商品打折,考虑这样的需求:
- 对于普通会员,所有商品打9折
- 对于钻石会员,在九折的基础上,当年每累积1000积分,额外打折1%
- 对于非会员客户,一般不打折
- 对于特价商品,对于所有人均5折销售
- 根据节假日、促销等情况,可以有灵活的打折方式供选择
可以看到,此需求要求商品打折的灵活性非常高,适合利用策略模式将其抽象出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# -*- coding: UTF-8 -*- from abc import abstractmethod # 打折策略接口 class DiscountStrategy: @abstractmethod def discount( self , oriPrice ): pass # 清仓打折策略 class ClearanceDiscountStrategy( DiscountStrategy ): def discount( self, oriPrice ): return oriPrice * 0.5 # 会员打折策略 class MemberDiscountStrategy( DiscountStrategy ): def discount( self, oriPrice ): return oriPrice * 0.9 # 钻石会员打折策略,必须感知上下文才能完成算法 class SeniorMemberDiscountStrategy( DiscountStrategy ): def __init__( self ): self.context = None def discount( self, oriPrice ): return oriPrice * 0.9 * ( 1 - self.context.point / 1000 / 100.00 ) # 折扣上下文接口 class DiscountContext: def __init__( self, strategy ): self.strategy = strategy self.memberType = None self.point = 0 def doDiscount( self, price ): print "Original price: %d, After discount: %d" % ( price, self.strategy.discount( price ) ) if __name__ == '__main__': # 钻石会员打折 strategy = SeniorMemberDiscountStrategy() ctx = DiscountContext( strategy ) strategy.context = ctx ctx.memberType = 2 ctx.point = 3001 ctx.doDiscount( 1000 ) |
C++语言的策略模式
如果不需要在运行时切换策略的实现,可以使用C++的模板机制,将Context与Strategy进行编译时绑定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <iostream> using namespace std; template<typename Strategy> class Context { private: Strategy strategy; public: void contextInterface() { this->strategy.algorithmInterface(); } }; class ConcreteStrategyA { public: void algorithmInterface() { cout << "algorithm a" << endl; } }; int main( int argc, char **argv ) { Context<ConcreteStrategyA> ctx; ctx.contextInterface(); } |
这种实现方式的缺陷是,不能在运行时切换策略类。
经典应用
布局管理器
UI框架中的布局管理器,通常以策略模式来实现,例如Java中的AWT:
以及ExtJS框架中的容器、布局组件层次:
ExtJS的设计中,策略类需要的数据,被封装在ContextItem类中,这个类相当于从上下文(容器)对象中分离出的一部分属性。
模式演变
- 退化:如果去除上下文,那么策略模式就变成了简单的接口实现层次,依据可以享受面向接口编程的好处。但是,没有上下文,就意味着客户端需要直接和策略类打交道
- 与模板方法模式结合:如果不同的具体策略,存在很多公共功能并且算法步骤类似,那么可以对策略类层次使用模板方法模式,将策略接口改为抽象类,并在此抽象类中实现骨架功能
Leave a Reply