4.策略模式(Strategy)

本文讲述了组件协作模式中的策略模式,介绍了策略模式的动机、定义、结构、代码实例,最后进行了总结。
再次感谢GeekBand的李建忠老师、GOF等前辈

1. “组件协作”模式:

  • 现代软件专业分工之后的第一个结果是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
  • 典型模式
    • Template Method
    • Strategy 策略模式
    • Observer / Event

2. 动机(Motivation)

  • 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担[稍后解释]
  • 如何在运行时根据需要透明地更改对象的算法?将算法与对象本身解耦,从而避免上述问题?

3. 模型代码

代码strategy1.cpp展示的是采用结构化软件设计方法实现的“税收计算”,首先使用枚举TaxBase来标记不同地区,在SalesOrder类的CalculateTax()中通过判断标记类型来使用不同国家的税收计算方法。

// strategy1.cpp
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){  //更改
			//...
		}
        //....
     }
};

使用设计模式的目的就是为了隔离变化,因此首先要找到代码变化点,为此我们心中要有一个时间轴,不要静态的看待一个代码,而要考虑代码在未来可能发生的变化,或者新增需求,从而找到变化点,在这个地方使用设计模式才会起到效果。比如现在有新需求要添加其它国家税收计算方法,如果在strategy1.cpp代码上继续扩展业务,则需要修改枚举类型,在CalculateTax()中也要增加相应的判断条件。
 
上述的修改方法明显违背了我们前边所说的“开放封闭原则[对扩展开放,对修改关闭,即类模块应该尽可能的使用扩展的方法来实现未来的变化,而不是直接修改编译好的代码]
 
代码strategy2.cpp展现的是我们使用面向对象软件设计方法实现的税务计算,通过观察发现strategy1.cpp中造成代码变化的点就是“不同的税收计算策略”,因此定义了一个TaxStrategy作为父类,为税收算法定义抽象接口Calculate(),这样我们通过继承、重写虚函数不仅可以实现代码扩展,而且也隔离了变化,比如定义CNTaxUSTaxDETaxFRTax分别表示不同国家税收计算方法。
 
SalesOrder中使用策略比较灵活,该类中聚合一个TaxStrategy对象指针[为了保证多态,必须使用指针或者引用,但是引用可能产生其它问题,没有指针方便]来保证调用正确的税务计算方案,另外我们在代码中还嵌入了工厂模式,使得我们在调用时不用直接new对象,只需要传入合适的参数便可以创建指定类型的堆对象[关于工厂模式的使用之后详细说明]

// strategy2.cpp
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); //多态调用
        //...
    }
};

4. 模式定义

定义一系列算法,把它们一个个封装起来,并且使它们可互相替换(变化)。该模式使得算法[不同国家的税务计算方法]可独立于使用它的客户程序(稳定)[税务计算功能调用点]而变化(扩展,子类化)。

5. 结构

在这里插入图片描述
结合Template Method一文中提到的理解设计模式的方法,我们需要知道那些部分是变化的,那些部分是稳定的。在Strategy模式类图中,红色部分是相同算法的不同实现版本,可以相互替换,而绿色部分是稳定的,不会随实现版本的变更而发生任何变化。

6. 要点总结

  • Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。
  • Strategy模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合[注意当if-else/switch-case在保证绝对不变的情况下才会继续保持,否则我们会闻到代码的bad small,想一想此时是否可以使用strategy模式代替吧]。含有许多条件判断语句的代码通常都需要Strategy模式。
  • 上面动机我们提到了Strategy模式的使用有两个优势,第一个就是算法的切换不会影响客户端,另一个就是可以提高性能,因为如果使用if-else等结构化的设计方法,我们会将大量不会使用的代码加载到高速缓存当中,这就导致效率的下降,因此要保证代码的本地化,使用策略模式可以保证只会加载相应的处理代码进缓存。
  • 如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值