1.模式定义
定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。
-《设计模式》GoF
这个定义很抽象,看不懂没关系,下面我们将以实例说明策略模式的思想。
假设我们的软件系统中,需要计算应纳税额,由于不同国家税法计算公式差别很大,因此需要分别处理。
2.初级的代码
没有设计模式思维的人,会自然而然的想到一个方法:枚举+判断。首先,枚举出需要实现哪些国家的税法。
enum TaxBase {
CN_Tax,
US_Tax,
DE_Tax
};
然后,通过条件判断语句(if语句或swith语句),返回相应的税额。其中,每个判断语句中分别实现不同国家税法。
class SalesOrder {
private:
TaxBase tax;
public:
double CalculateTax()
{
if (tax == CN_Tax)
{
}
else if(tax == US_Tax)
{
}
else if(tax == DE_Tax)
{
}
}
};
这种实现方案初看起来没有问题,但是当考虑到业务变化时,就会发现它的不足之处。
假设我们的业务扩张到了法国,那么需要增加法国税额计算。在上述设计方案下,我们需要增加一个枚举类型FR_Tax,增加一个条件判断else if (tax == FR_Tax) {},并在判断语句中计算法国的税额。然后代码需要重新编译,重新测试,重新部署。
上述方法明显违背了软件设计原则中的开闭原则,即对扩展开放,对更改封闭。
3.高级的代码
有设计模式思维的人,就会考虑用策略模式实现上述需求。首先,定义一个基类。
class TaxStrategy
{
public:
virtual double Calculate(const Context& context)=0;
virtual ~TaxStrategy() {}
};
注:纯虚方法有=0的标记,必须使用虚析构函数,否则delete会出问题。
然后,不同国家继承上述基类,实现税额的计算。
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) //用工厂方法生成对象指针
{
strategy = strategyFactory->NewStrategy();
}
~SalesOrder()
{
delete strategy;
}
double CalculateTax()
{
Context context; //上下文
double val = strategy->Calculate(context); //多态调用
return val;
}
};
现在,同样考虑业务扩展的情况,需要增加法国税额的计算,现在我们只需要增加一个新类FRTax,SalesOrder 等其它类不再需要变化。用扩展的方式支持未来的变化,而不是修改源代码的方式,这种方案遵循了开闭原则,增强了软件的复用性。
class FRTax :public TaxStrategy {
public:
virtual double Calculate(const Context& context) {}
};
4.总结
策略模式下,基类及其子类为组件提供了一系列可以重用的算法,从而在运行时方便地根据需要在各个算法之间切换。
策略模式提供了用条件判断语句之外的另一种选择,消除条件判断语句,就是在解耦合。含有多个条件判断语句的代码通常都需要考虑策略模式。