1、定义与动机
-
定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化),该模式使得算法可独立于使用它的客户程序(稳定)而变化(扩展,子类化)
-
在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都硬编码到对象中,将会使对象变得异常复杂;而且有时候支持不适用的算法也是一个性能负担(代码段过长)
-
如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?
2、举例
- 对于一个跨境电商的软件来说计算不同国家的税收、货币结算是一个非常常见的事情
- 而通产来说每个国家的税的计算方式并不相同,每个国家都有其自己的税换算算法
- 这里的设计也存在一定的门路,如果换成代码层面来看
2.1、结构化软件设计流程
这种做法很简单:
- 首先定义一个枚举类,将不同国家的税率计算方式定义出来(这个可以不需要)只是为了使用方便
- 然后定义销售订单类,里面定义计算税的方法,具体的实现通过大量的if-else if-else来具体调用
- 对于这样的一个计算税的方法,calculate方法过于庞大,主要存在两个问题:
- 违背开闭原则(OCR):当这个海外电商需要新增一个上线的国家,那么需要修改TaxBase枚举类,其次需要在计算方法里面增加这个国家的税计算方式,很明显违背修改关闭拓展开放的原则
- 性能负担:其实这个软件在一个国家上线后大量的其他国家的税计算方式并不关心,但是在代码中强行硬编码写入!运行起来的代码段充斥着大量的无关代码!导致性能的下降
enum TaxBase{CN_Tax, // 国内税US_Tax, // 美国税DE_Tax // 德国税/*如果需要增加,需要在这里继续枚举的定义....JP_Tax,FR_Tax*/
};class SalesOrder{
private:TaxBase tax;
public:double calculateTax(){//...if(tax == CN_Tax){// CN*****}else if(tax == US_Tax){// US*****}else if(tax == DE_Tax){// DE*****}// 这里需要增加拓展国家税的计算方式}
};
2.2、策略模式
- 策略模式:当代码中存在大量的if-else if-else或者switch-case时,就应该考虑使用策略模式来优化
- 实现思路思考:
- 首先定义一个计算税的抽象基类TaxStrategy
- 对于不同的国家计算方式定义不同的类继承TaxStrategy基类,并且实现其计算calculateTax方法
- 然后在需要使用计算的代码中聚合/组合一个基类TaxStrategy的指针,如果注入的方式初始化!
- 最后在需要的地方通过基类指针TaxStrategy调用对应的不同国家的方法。
- 核心点主要有几地方:
- 所有国家的税收计算类都继承基类,并且实现基类的抽象方法
- 在需要使用的地方定义基类对象的指针,通过注入的方式传入需要的子类对象(多态)
- 最后调用方法通过虚函数表来调用实际传入对象的计算方法(动态绑定、晚绑定)
- 策略模式提倡的互相替换在这里通过多态来实现,替换体现在实际传入初始化的对象到底是哪一个!
- 这样做的好处:
- 如果需要增加新的国家,可以看到大部分代码都是不用修改的,只需要扩展新的类即可
- 性能的优化,实际说在使用的过程中只需要把需要的对象new出来即可
class TaxStrategy {
public:virtual double calculateTax() = 0;virtual ~TaxStrategy();
};class CNTax: public TaxStrategy{
public:double calculateTax() override {// 国内税计算}
};class USTax: public TaxStrategy{
public:double calculateTax() override {// 美国税计算}
};class DETax: public TaxStrategy{
public:double calculateTax() override {// 德国税计算}
};
class JPTax: public TaxStrategy{
public:double calculateTax() override {// 日本税计算}
};class SalesOrder{
private:TaxStrategy *taxStrategy; // 基类指针
public:SalesOrder(TaxStrategy *_taxStrategy): taxStrategy(_taxStrategy){}virtual ~SalesOrder(){delete taxStrategy;taxStrategy = nullptr;}double calculate(){// ...// 还是动态绑定通过虚函数来调用实际的传入对象的calculateTax方法double val = taxStrategy->calculateTax();// ...return val;}
};
通过这个UML图可以看到TaxStrategy和SalesOrder是相对稳定的,而不稳定的是TaxStrategy的子类
3、总结
-
Strategy及其子类为组件提供了一系列的可重用的算法,从而可以使得类型在运行时方便地根据需要再各个算法之间进行切换
-
Strategy模式提供了用条件判断语句意外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式
-
如果Strategy对象没有实例化变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。