策略模式,英文是:Strategy Design Pattern。在《设计模式》中的定义:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端。

动机

  • 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一种性能负担。

  • 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述的问题?

定义

策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。因为所有的策略类都实现相同的接口,所以,客户端代码基于接口而非实现编程,可以灵活地替换不同的策略。

GoF中的定义:定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)。

实例

用枚举的方法列举不同国家的税收方式,我们使用 if-else 的处理了中国,美国,德国的税收情况。随着时间的推移,我们需要增加其他国家的税收处理,这时候我们需要修改基础枚举,修改 if 的判断条件。违背了策略模式的开闭原则。

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) { // 更改
      // FR***
    }
  }
};

我们将上面代码进行更新,当我们需要新增税收的时候,只需要重新声明一个 FRTax 的类。

class TaxStrategy {
public:
  virtual double Calculate(const Context &context) = 0;
  virtual ~TaxStrategy() {}
};

class CNTax : public TaxStrategy {
public:
  virtual double Calculate(const Context &context) {
    // CN***
  }
};

class USTax : public TaxStrategy {
public:
  virtual double Calculate(const Context &context) {
    // US***
  }
};

class DETax : public TaxStrategy {
public:
  virtual double Calculate(const Context &context) {
    // DE***
  }
};

// 更改
class FRTax : public TaxStrategy {
public:
  virtual double Calculate(const Context &context) {
    // FR***
  }
};

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); // 多态调用

    //...
  }
};

策略的创建

因为策略模式会包含一组策略,在使用它们的时候,一般会通过类型(type)来判断创建哪个策略来使用。为了封装创建逻辑,我们需要对客户端代码屏蔽创建细节。我们可以把根据 type 创建策略的逻辑抽离出来,放到工厂类中。

在工厂类中,我们用 Map 来缓存策略,根据 type 直接从 Map 中获取对应的策略,从而避免 if-else 分支判断逻辑。

结构:

┌──────────────────┐ strategy  ┌─────────────────────┐
│Context           ├─────────> │Strategy             │
├──────────────────┤           ├─────────────────────┤
│ContextInterface()│           │AlgorithmInterface() │
└──────────────────┘           └─────────────────────┘
                                          ▲
                                          │
             ┌────────────────────────────┼───────────────────────────┐
             │                            │                           │
   ┌─────────┴───────────┐     ┌──────────┴──────────┐    ┌───────────┴─────────┐
   │ConcreteStrategyA    │     │ConcreteStrategyB    │    │ConcreteStrategyC    │
   ├─────────────────────┤     ├─────────────────────┤    ├─────────────────────┤
   │AlgorithmInterface() │     │AlgorithmInterface() │    │AlgorithmInterface() │
   └─────────────────────┘     └─────────────────────┘    └─────────────────────┘

要点总结

Strategy 及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。

Strategy 模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦。含有许多条件判断语句的代码通常需要 Strategy 模式。

如果 Strategy 对象没有实例变量,那么各个上下文可以共享同一个 Strategy 对象,从而节省对象开销。

参考链接