一、 策略模式动机
- 软件构建中,某些对象算法可能多种多样,经常改变,如果将它们都编码到对象中,会使得对象非常复杂,有时支持不适用的算法也会造成性能负担;
- 如何在运行时透明地更改对象的算法,将算法与对象本身解耦,从而避免上述问题?
二、策略模式定义( GOF定义)
定义一系列算法,把他们一个个封装起来,并且使他们可互相替换(变化)。该模式使得算法可独立于使用他们的客户程序(稳定)而变化.
三、代码示例
3.1 重构前代码
#include <stdio.h>
enum TaxBase // 税种的枚举体
{
CN_Tax = 0,
US_Tax = 1,
DE_Tax = 2
};
class SalesOrder
{
public:
SalesOrder(TaxBase tax) { this->tax = tax; }
~SalesOrder() {}
public:
double CalculateTax()
{ // 根据税种类不同,计算税额,不同国家算法不同
if (tax == CN_Tax) {} // 情况1...
else if (tax == US_Tax) {} // 情况2...
else if (tax == DE_Tax) {} // 情况3...
return 0;
}
private:
TaxBase tax;
};
int main()
{
SalesOrder sales_order(TaxBase::CN_Tax);
sales_order.CalculateTax();
renturn 0;
}
3.2 问题思考
程序咋一看没什么问题,但是否考虑到程序的后续可扩展性?比如增加一些国家(枚举体),也会增加相应的算法;
违背原则:多扩展开放、对修改封闭. 本示例代码中,如将来再实现其它国家税率计算的扩展,doubleCalculateTax()的代码就需要跟着相应变化.
3.3 重构后代码
#include <stdio.h>
// 定义基类,抽象税率计算统一的格式
class TaxStrategy {
public:
virtual double CalculateTax(const Context& contex) = 0; // 纯虚方法
virtual ~TaxStrategy() {} // 良好的习惯,基类析构都写成虚函数
};
// 添加扩展1:中国税率计算类
class CNTax :public TaxStrategy {
public:
virtual double CalculateTax(const Context& contex) { // 实现接口类的纯虚函数
// Override:实现China的算法
return 0;
}
};
// 添加扩展2:美国税率计算类
class USTax :public TaxStrategy {
public:
virtual double CalculateTax(const Context& contex) { // 实现接口类的纯虚函数
// Override:实现US的算法
return 0;
}
};
// 添加扩展3:德国税率计算类
class DETax :public TaxStrategy {
public:
virtual double CalculateTax(const Context& contex) { // 实现接口类的纯虚函数
// Override:实现DE的算法
return 0;
}
};
// 这里,还可以添加任何扩展
// 即使增加了国家,SalesOrder类也不需要发生任何变化
class SalesOrder
{
public:
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder() { delete this->strategy; }
public:
double CalculateTax()
{
Context contex;
double val = strategy->CalculateTax(contex);
return val;
}
private:
TaxStrategy* strategy; // 税率策略,这是一个多态指针,如果放一个对象就没有多态性了
};
// 工厂类模式将再后面的章节讲解,这里暂时先认为它是一个产生抽象对象的工具
class StrategyFactory
{
public:
StrategyFactory() {}
~StrategyFactory() {}
public:
TaxStrategy* NewStrategy() {
// 后面讲解工厂模式的时候再添加,这里是策略模式的重点,怎么在二进制级别复用,如不停机扩展
}
};
// 真正的复用指的是二进制意义的复用,而不是源代码级别的复用
int main()
{
StrategyFactory* strategyFactory;
SalesOrder2 sales_order2(strategyFactory);
sales_order2.CalculateTax();
return 0;
}
四、策略模式要点总结
- Strategy及子类为组件提供了一些列可重用的算法,从而使得类型在运行时方便根据需要在各算法之间切换;
- 策略模式提供了用条件判断语句以外的一种可扩展的选择,消除条件语句的耦合性,含有多个条件分支的代码通常都需要策略模式;
- 如果策略对象没有实例变量,那么各个上下文可共享一个策略对象,从而节省对象开销.
五、 思路总结
- 使用策略模式重构if语句的情形,在于if的情形始终存在变化的情形;
- 如果if分支的情况永远不变,则if分支即可; Case1
- 如果if分支不变,但在某些情形下某些分支很大概率不会被执行,这是可能造成性能负担,这是也可以使用策略模式. Case2