C++设计模式——策略模式Strategy-Pattern
动机(Motivation)
- 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担。
- 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
模式定义
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。 ——《设计模式》 GoF
策略模式的本质是使用多态实现程序的扩充性。这里举一个不使用策略模式和使用策略模式的例子。
模式举例
我们知道大型的跨国公司在面对不同国家的不同税法要求需要有不同的策略来应对,假如该公司之前只有中国、美国和德国的税收应对策略,现在由于要在法国开分公司,因为要实现对法国税收的支持。如果我们不使用策略模式,很有可能使用下面的代码,增加支持法国的业务,那么就在枚举类型上加上法国,if-else上增加法国相关的代码,但这样设计其实有很大的不妥,违背了设计模式中对扩展开放,对修改封闭的原则。
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); //多态调用 //... } };
可以看到我们使用策略模式重构后只需要派生一个新的子类即可,通过多态调用真正实现复用。
我们知道业务场景的变化可能是非常迅速的,使用if-else要特别小心,这时候要考虑能否使用策略模式设计。
在不使用策略模式的上例中,其实还有对内存资源占用过大的缺点,比如只是使用美国的业务,但却要将整个判断代码存入内存中,判断代码中有所有的应对策略,但只需要其中的一种,无疑是一种浪费。
要点总结
- Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。
- Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。
- 如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。
基本代码
#include <iostream> using namespace std; class Strategy { // 抽象算法类 public: virtual void AlgorithmInterface() = 0; // 算法方法 virtual ~Strategy(){} }; class ConcreteStrategyA : public Strategy { // 具体算法A public: void AlgorithmInterface() { cout << "ConcreteStrategyA" << endl; } }; class ConcreteStrategyB : public Strategy { // 具体算法B public: void AlgorithmInterface() { cout << "ConcreteStrategyB" << endl; } }; class Context { // 上下文 private: Strategy* strategy; public: Context(Strategy* s) { strategy = s; } void ContextInterface() { // 上下文接口 strategy->AlgorithmInterface(); } }; int main() { Strategy* s = new ConcreteStrategyA(); Context* c = new Context(s); c->ContextInterface(); // ConcreteStrategyA delete s; delete c; return 0; }