策略模式
策略模式仍然是一个组件协作的设计模式, 和模板方法模式有异曲同工之妙
动机
在软件构建的过程中, 某些对象的算法可能多种多样, 经常改变 如果将这些算法都编码到对象中,
将会使对象变得异常复杂, 而且有时候不支持使用的算法也是一种负担
那么:
- 如何在运行时, 根据需要透明地更改对象的算法?
- 如何将算法和对象本身解耦, 从而避免上述问题?
代码
假设有一种场景, 在一个电子商务中, 需要做订单的计算, 其中有一个重要的税务计算
假如我们支持跨国的计算, 那么需要考虑不同国家税的计算
eval.cpp
enum TaxBase
{
CN_Tax,
US_Tax,
DE_Tax
};
class SalesOrder
{
TaxBase tax_;
public:
double CalculateTax()
{
// code...
if (CN_Tax == tax_)
{
// CN code...
}
else if (US_Tax == tax_)
{
// US code...
}
else if (DE_Tax == tax_)
{
// DE code...
}
}
};
以上代码没啥问题, 都能解决问题, 但是作为一个程序员, 需要动态的看待问题
如果有一些变化呢?
如果未来要支持日本或者法国的税收计算呢?
- 更改枚举类型 【违背开闭原则】
- 更改if-else语句 【违背开闭原则】
那么如何解决这个问题呢?
eval.cpp
class TaxStrategy
{
public:
virtual double Calculate(const Context& ctx) = 0;
virtual ~TaxStrategy() = default;
};
class CNTax : public TaxStrategy
{
public:
virtual double Calculate(const Context& ctx)
{
// code...
}
};
class USTax : public TaxStrategy
{
virtual double Calculate(const Context& ctx)
{
// code...
}
};
class DETax : public TaxStrategy
{
public:
virtual double Calculate(const Context& ctx)
{
// code...
}
};
class SalesOrder
{
TaxStrategy* strategy_; // 指针实现多态
public:
SalesOrder(TaxStrategy* strategyFactory)
{
this->strategy_ = strategyFactory->NewStrategy(); // 利用工厂模式来生产对象
}
~SalesOrder()
{
delete this->strategy_;
}
double CalculateTax()
{
// code...
Context context();
double val = strategy_->Calculate(context); // 调用策略
// code...
}
}
很显然, 此时要增加一个新的国家的税收, 只需要实现一个新的子类, SalesOrder根本不需要改动
思考
那么回到开头的问题
- 如何在运行时根据需要透明地更改对象的算法?
A: 策略模式讲究的是定义一系列算法, 把它们一个一个封装起来, 并且它们可以互相替换(变化), 该模式使得
算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)
- 如何将算法和对象本身解耦, 从而避免上述问题?
策略模式一般只会有一个方法, 这个方法就是策略(变化, 也就是上面程序里的不同国家的税收)
策略模式及其子类为组件提供了一系列可重用的算法, 从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换
策略模式提供了条件分支语句以外的另一种选择, 消除条件分支语句,就是解耦合
因此,当你的代码里出现许多if-else, 这就是策略模式需要的特征, 你可以尝试使用策略模式重构这段代码
为什么在面向对象里, 策略模式比if-else更胜一筹?
因为if-else是结构化时代的一种分而治之的思想, 而策略模式是OO时代
再者, if-else, if-else, 许多以后, 你有没有想过, 会不会有更多的if-else出现?
那么出现if-else就要考虑策略模式吗?
不对, 如果你的if-else是绝对不变的情况下, 就没必要, 例如, 你的if-else 是根据性别进行变化的时候
怎么, 你的性别不是男女?
特别的,有时候支持不使用的算法也是一种负担这句话
例如, 你的税收系统, 装在中国, 在第一个代码片里, 后续的else if都不会得到执行
所以这个时候就会有性能负担, 并且很多代码被装载了, 浪费了代码段的代码段的空间
而策略模式不一样, 代码段放在缓存里是最好的, 再次放在主存里, 甚至可能主存都放不下
但是策略模式就能省一些代码段的存储空间
最后, 如果Strategy对象没有实例变量, 那么各个上下文可以共享一个Strategy对象, 避免了Strategy对象的实例化和内存的消耗【单一对象】