2018-09-21 15:54:34
策略模式
工厂系列模式只是解决了对象创建的问题。策略(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。算法本身是一种策略,而且这种策略是随时都可能相互替换的,这就是变化点,而封装变化点是面向对象的一种很重要的思维方式。策略模式是一种定义一系列算法的方式,从概念上卡,所有这些算范完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
UML类图
Context:上下文,用一个ConcreteStrategy来配置,持有一个Strategy的引用,最终交给客户端代码使用
Strategy:策略类,定义所有支持的算法的公共接口
ConcreteStrategy:具体策略类,封装了具体的算法或行为,继承于Strategy。
策略模式的优缺点
优点:
1.策略模式的Strategy类层次为Context定义了一些列的可供重用的算法或行为。继承有助于析取出这些算法的公共功能。
2.简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
3.当不同的行为堆砌在一个类中时,很难避免使用条件语句来选择合适的行为。比如你需要在客户端代码中来进行选择使用哪一种算法。将这些行为封装在一个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。策略模式就是用来封装算法的,但在实践中,我们发现可以用它来分装几乎任何类型的规则,只要在分析过程中听到需要在不同实践应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
缺点:
1.在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象,这本身并没有解除客户端需要选择判断的压力(也就是说客户端需要知道所有的策略类型)。但是可以通过工厂模式进行一定程度的改进。
2.但策略类增加时,将可能产生大量的子类,如果一个算法被多次使用,但仅是外部条件不同的时候,可以考虑使用享元模式来进行优化,减少实例的个数,但这在涉及线程安全的时候需要格外注意。
适用场景:
1.需要在不同的时间应用不同的业务规则时可以考虑使用策略模式,以后规则还有可能增加,且每种规则都可以抽象成独立的类
2.对客户端隐藏实现算法的细节,是客户端和算法之间完全独立
代码示例
超市打折策略:正常收费、消费满指定金额优惠一定金额、消费满指定金额给予一定的折扣。
1.所有策略的基类(UML类图中的Strategy)
#ifndef STRATEGY_H_ #define STRATEGY_H_ class Strategy { public: virtual double acceptCash(const double iOriginal) const = 0; Strategy() = default; virtual ~Strategy() = default; }; #endif
2.超市的三种打折策略(UML类图中的ConcreteStrategy)
#ifndef CASHNORMAL_H_ #define CASHNORMAL_H_ #include "Strategy.h" class CashNormal : public Strategy { public: double acceptCash(const double dOriginalCash) const override { return dOriginalCash; } CashNormal() = default; ~CashNormal() = default; }; #endif #ifndef CASHREBATE_H_ #define CASHREBATE_H_ #include "Strategy.h" class CashRebate : public Strategy { public: double acceptCash(const double iOriginalCash) const { if(iOriginalCash > m_dCondition) return iOriginalCash * m_dRebate; return iOriginalCash; } CashRebate(double dCondition,double dRebate):m_dCondition(dCondition),m_dRebate(dRebate){}; ~CashRebate() = default; private: double m_dCondition{0}; //if cash > m_dCondition,you can have a discount double m_dRebate{1}; //discount as m_dRebate }; #endif #ifndef CASHRETURN_H_ #define CASHRETURN_H_ #include "Strategy.h" class CashReturn : public Strategy { public: double acceptCash(double dOriginalCash) const override { if(dOriginalCash > m_dCondition) return dOriginalCash - dOriginalCash/m_dCondition*m_dReturn; return dOriginalCash; } CashReturn(const double dCondition,const double dReturn): m_dCondition(dCondition), m_dReturn(dReturn){} ~CashReturn() = default; private: double m_dCondition{1}; double m_dReturn{0}; }; #endif
3.上下文类(负责和客户代码打交道,UML类图中的Context)
#ifndef CONTEXT_H_ #define CONTEXT_H_ #include "Strategy.h" class Context { public: double contextInterface(double dOriginalCash); Context(Strategy * pConcreteStrategy) { m_pStrategy = pConcreteStrategy; } ~Context() { } private: Strategy* m_pStrategy{nullptr}; }; #endif #include "Context.h" double Context::contextInterface(double dOriginalCash) { if(nullptr == m_pStrategy) return 0; return m_pStrategy->acceptCash(dOriginalCash); }
4.Client
#include "Context.h" #include "CashNormal.h" #include "CashRebate.h" #include "CashReturn.h" #include <iostream> #include <string> using namespace std; const std::string RETURN = "return"; const std::string REBATE = "rebate"; const std::string NORMAL = "normal"; int main(int argc,char *argv[]) { if(argc != 2) { cout << "Param count is error!" <<endl; } string strCommand = argv[1]; if(strCommand == RETURN) { CashReturn objCashReturn(300,100); Context objCashContext(&objCashReturn); cout << "Amount receivable :" << objCashContext.contextInterface(400); } else if(strCommand == REBATE) { CashRebate objCashRebate(300,0.8); Context objCashContext(&objCashRebate); cout << "Amount receivable :"<< objCashContext.contextInterface(350); } else if(strCommand == NORMAL) { CashNormal objCashNormal; Context objCashContext(&objCashNormal); cout << "Amount receivable :" << objCashContext.contextInterface(500); } else cout << "Command is Error " << endl; return(1); }
总结:其实从这个小例子能看出来策略模式的最大优点是通过Context类隔绝了策略的具体实现和客户端代码的关系,并有利于横向扩展,但这里的问题是,客户代码必须要知道每一个策略类叫什么名字,才能正确的使用这个策略。优化的时候可以考虑使用工厂模式,但使用工厂模式的话,必须要考虑,怎样把策略需要满足的条件传递过去,使得整个模式能够正常的工作。