定义一系列算法,把他们一个个封装起来,并且是他们可以互相替换(变化)。该模式似的算法可以独立于使用它的客户程序(稳定的)而变化(扩展,子类化)。
——《设计模式》GoF
- 动机(Motivation)
在软件构建的过程中,某些对象使用的算法可能是多种多样的,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一种性能负担
比如现在需要有一个税种的计算系统,其中支持计算各国的税种,那么他的伪码描述就是如下:
enum TaxBase {
CN_Tax,
US_Tax,
DE_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***********
}
//....
}
};
但是随着时间的推移,这些税种对我们来说已经不能满足我们的要求了,比如需要增加一个税种的计算,那么我们的伪码恐怕会编程以下这样了
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){ //更改
//...
}
//....
}
};
我们不但需要修改TaxBase中的参数,还需要在SalseOrder中添加关于新增加税种的一个判断,然后才能进行相应的计算。
对于上面这个伪码描述来说,修改的地方也是比较多的,基本上是牵一发而动全身了。并且,在SalesOrder中,CalculatetTax函数中每次调用其实只会使用其中的一个分支,那么其余的分支每次也需要被加载到内存中去,其实也会是一种资源的浪费。
也就是他违反了对开闭原则,同时也违反了反向依赖的原则。
那么我们尝试另外一种方式
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 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); //多态调用
//...
}
};
那么此时如果需要增加一个新的税种,该如何增加呢,那么伪码描述就下下方了。
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); //多态调用
//...
}
};
对于第二个方法来说,SalesOrder的CalculateTax函数来说,他多态调用了TaxStrategy类中的Calucate方法。而TaxStrategy来说他的每个子类都是一种新的税种计算方式,每个税种计算中还实现了自己对应的税种计算的方法。
在扩展的时候,需要增加TaxStrategy的子类就行了,不需要在改变SalesOrder的中的代码了。
要点总结
- Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便的根据需要在各个算法之间进行切换
- Strategy模式提供了用条件判断语句的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。尤其是条件判断语句在未来会有增加可能性的时候,应该优先考虑Strategy模式。
- 如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象的开销。