设计模式---组件协作模式之策略模式(Strategy)

一:概念

Strategy模式,对一系列的算法加以封装,为所有算法定义一个抽象的算法接口,并通过继承该抽象算法接口对所有的算法加以封装和实现,具体的算法选择交由客户端决定(策略)。
Strategy模式主要用于平滑的处理算法的切换

二:动机

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

三:代码解析(税种计算)

(一)结构化代码

1.原代码

enum TaxBase {
    CN_Tax,
    US_Tax,
    DE_Tax,
};

class SalesOrder{
    TaxBase tax;
public:
    double CalculateTax(){
        //...
        
        if (tax == CN_Tax){  //或者switch开关语句
            //CN***********
        }
        else if (tax == US_Tax){
            //US***********
        }
        else if (tax == DE_Tax){
            //DE***********
        }//....
     }
    
};
不要静态的去看一个软件结构的设计,而是要动态的去看,这是设计模式的一个重要观点。

2.需求变化,需要支持法国税法

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

        //....
     }
    
};

我们可以发现上面的修改违反了一个原则:开放封闭原则(重点)

对扩展开发,对更改封闭。类模块尽可能用扩展的方式来支持未来的变化,而不是找到源代码,用修改源代码的方式来面对未来的变化
上面代码两处红色部分就违反了开放封闭原则,带来了一个很大的复用性负担,软件要重新更改,重新编译,重新测试,重新部署,需要的代价十分大。
所以尽可能使用扩展方式来解决问题,如何使用扩展方式来解决,就要用到Strategy模式

(二)面向对象Strategy模式代码

1.原代码

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 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); //多态调用,具体依赖于工厂创建的对象
        //...
    }
    
};
我们将原来结构化设计中的一个个算法,变为TaxStrategy类的一个子类
相对于上面结构化思想相比较:功能是一样的。但是要比较好处,在设计领域要比较好处需要放在时间轴中去看

2.修改代码,现在需要支持法国税务算法,进行扩展(新的文件)

//扩展
//*********************************
class FRTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //.........
    }
};
其他地方不需要修改,当然工厂中需要去修改使得能够产生法国税务对象
多态调用会自然找到我们扩展的税务算法运算
在整体代码中,尤其是SalesOrder主调用类中,代码不需要改变,得到了复用性
注意:我们在新的文件中写入我们扩展的算法,单独编译,例如dll,动态加入程序中,遵循了开闭原则
而第一种方法中在方法内部去修改添加源代码,打破了开放封闭原则,违背了复用性

复用性:

注意:我们在面向对象中(尤其是设计模式领域中)谈到的复用性是指编译单位,二进制层次上的复用性
例如我们第二种方法,在编译部署后,不需要修改原来的文件,我们只需要将新添加的扩展单独编译,动态被调用即可。原来二进制文件的复用性很好。
但是对于第一种方法,我们对原方法进行了修改,我们的整个程序都要重新编译,部署,原来的二进制文件不再使用,而是需要我们新的编译后的二进制文件,所以复用性极差

例如下面的修改:

        if (tax == CN_Tax){
            //CN***********
        }
        else if (tax == US_Tax){
            //US***********
        }
        else if (tax == DE_Tax){
            //DE***********
        }
        else if (tax == FR_Tax){  //更改
            //...
        }
而且在下面补充一段代码是很有问题的,虽然我们保证上面代码不变,但是在实际开发中,向后面添加代码,往往会打破这个代码前面的一些代码,会向前面引入一些bug。而且我们对这种不叫做复用,真正的复用是编译层面的

四:模式定义

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

五:类图(结构)

Strategy基类中一般放置的方法不多
除了极个别,像单例。其他的都可以像上面一样去找出它的稳定部分和变化部分

六:要点总结(优点)

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

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); //在运行时就支持多态调用,灵活变化
        //...
    }
    
};

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

一般来说代码出现if..else...或者switch...case...,这就是我们需要Strategy模式的特征。(当然我们说的是变化的,若是像男女性别判断,就是绝对不变的,就不需要使用到Strategy模式,但是更多场景都是可变的)
因为if..else..是结构化思维中的分而治之。而且需要一直判断,支持不使用的算法也是一个性能负担。而且由很大一段代码都被装载在代码段中,这是不需要的。
我们使用Strategy模式,是在运行时加载,运行中需要哪个就可以即时调用。具有稳定性。
在运行时代码在代码段中,放在内存中,最好的是加载在高级缓存中,最快,若是代码段过长,就只能放在主存,甚至虚拟内存中(硬盘),所以我们使用if...else..时,会有很多代码被加载到高级缓存,内存中,这会占用空间,其他代码就会被挤入其他地方,执行不会太快,Strategy模式会顺便解决部分这个问题

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

七:缺点

(一)客户端必须知道所有的策略类,并自行决定使用哪一个类。解决:我们可以使用工厂模式

(二)策略模式造成了很多的策略类。解决:通过把依赖于环境的状态保存到客户端,而将策略类设计成共享的,这样策略类实例可以被不同客户端使用(单例模式)

八:案例实现

//策略基类
class Strategy
{
public:
    virtual void SymEncrypt() = 0;
    virtual ~Strategy(){};
};
//各个算法
class Des:public Strategy
{
public:
    virtual void SymEncrypt()
    {
        cout << "Des Encrypt" << endl;
    }
};

class AES :public Strategy
{
public:
    virtual void SymEncrypt()
    {
        cout << "AES Encrypt" << endl;
    }
};
//上下文管理器
class Context
{
private:
    Strategy* s;
public:
    Context(Strategy* strategy)
    {
        s = strategy;    //这里应该由工厂实现
    }

    void Operator()
    {
        s->SymEncrypt();
    }
};
void main()
{
    Strategy* strategy = NULL;
    Context* ctx = NULL;

    strategy = new AES;
    ctx = new Context(strategy);
    ctx->Operator();

    delete strategy;
    delete ctx;

    system("pause");
    return;
}

 

posted @ 2018-08-24 11:04  山上有风景  阅读(558)  评论(0编辑  收藏  举报