1、动机
(a)在软件构建过程中,某些对象使用的算法可能多种多样,常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
(b)如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
2、定义
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。
3、举例
有这样一个场景,我们需要计算中国、美国、德国的国家税法,有一个SalesOrder类,我们实现cal方法,常用是if-else,或者switch-case,这样完全可以实现。
但是,我们做面向对象程序设计时,不应该静态的去看一个软件的设计,而是要动态来看,即要有时间轴的概念,要把思维上升到未来时间段。
现在需求更改,需增加法国的税法,那么我们在源代码基础上进行更改,enum中增加法国,calculate中增加else-if。这样做真的好吗?
enum TaxBase {
CN_Tax,
US_Tax,
DE_Tax,
FR_Tax //更改
};
class SalesOrder{
TaxBase tax;
public:
double CalculateTax(){
//...
if (tax == CN_Tax){
//CN***********
}
else if (tax == US_Tax){
//US***********
}
else if (tax == DE_Tax){
//DE***********
}
else if (tax == FR_Tax){ //更改
//...
}
//....
}
};
这里更改代码对软件来说并不具有良好的扩展性。代码应该用扩展来应对未来需求的变化,这样做违背开闭原则,这样做的结果是出现源码更改、软件重新编译、软件重新测试、软件重新部署等一系列代价。对于扩展,策略模式出来了。
class TaxStrategy{
public:
virtual double Calculate(const Context& context)=0;
virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class USTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class DETax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
//扩展
//*********************************
class FRTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//.........
}
};
class SalesOrder{
private:
TaxStrategy* strategy;
public:
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder(){
delete this->strategy;
}
public double CalculateTax(){
//...
Context context();
double val =
strategy->Calculate(context); //多态调用
//...
}
};
首先,我们实现了一个基类,析构函数声明为virtual,这里是为了避免调用多态时delete的问题,然后声明纯虚函数cal,几个类分别继承基类实现各自的方法。
当然,在实际开发过程中,这几个子类需要写在各自的.h和.cpp中。
当我们需要扩展的时候,就可以增加一个对应的子类来进行扩展实现,对于我们的工程而言,就可以弄成增加了一个动态库或者静态库而已。
然后,看一下SalesOrder的实现,在类中定义一个基类指针
TaxStrategy* strategy;
用于保存实例化出来的子类对象。当然这里对象的生成是放在工厂模式中生产出来。后续会讲解工厂模式
4、总结
(a)Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。
(b)Strategy模式提供了用条件判断语句以外的另一种选择,消除条件,判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。
但是当你的if-else在绝对不更改的情况下,就不用考虑策略模式。例如你的逻辑是根据性别来进行判断的。当然,以我们开发经验来讲,业务通常是可变的,所以
(c)如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。
这里顺便提升了代码的性能,可以这样理解,你的if-else始终只能调用一个逻辑,例如在某一次它调用了中国,那其他else-if里面的代码被装载到内存的代码段中,但我们并没有调用它,说白了,有很多的代码加载了而我们并没有使用,这在一定程度上产生了性能负担。
而使用策略模式以后,通过基类指针保存的子类对象类型直接调用指定类型的方法,这样代码段少。我们代码最好的情况是加载到cpu高级缓存中,如果代码段过长,有些就会放到主存或者更极端的放到虚拟内存硬盘中。这样肯定性能就不会提升。