设计模式 c++版(13)——策略模式

定义:
定义一组算法,将每个算法都封装起来,并且使他们之间可以互换


示例一:策略模式(通用版)


1. 类图18-3
设计模式 c++版(13)——策略模式

 

2. 类图说明

策略模式使用的就是面向对象的继承和多态机制

  • Context 封装角色。也叫上下文角色,起承上启下的封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
  • Strategy 抽象策略角色。策略、算法家族的抽象,通常为借口,定义每个策略或算法必须具有的方法和属性。
  • ConcreteStrategy 具体策略角色。实现抽象策略中的操作,该类含有具体的算法。


3. 代码清单18-3

////////////////////////    **********  1. 策略模式(通用版),代码清单18-3:***************//


//抽象的策略角色
class Strategy
{
public:
    virtual void    operate() = 0;
};

//具体策略角色
class ConcreteStrategy1: public Strategy
{
public:
    virtual void    operate(){qDebug() << "ConcreteStrategy1";}
};

//吴国太开绿灯
class ConcreteStrategy2: public Strategy
{
public:
    virtual void    operate(){qDebug() << "ConcreteStrategy2";}
};

//封装角色
class Context
{
public:
    Context(Strategy* strategy)
    {
        this->m_strategy = strategy;
    }
    void    operate()
    {
        this->m_strategy->operate();
    }

private:
    Strategy   *m_strategy;
};

//使用计谋
int main()
{
    Strategy *gy1      = new ConcreteStrategy1();
    Strategy *gy2    = new ConcreteStrategy2();
    
    Context *context = new Context(gy1);
    context->operate();
    
    context = new Context(gy2);
    context->operate();
    
    return 0;
}

 

4. 代码分析

策略模式就是采用了面向对象的继承和多态机制。在真实的业务环境中,需要看清楚哪个接口是抽象策略接口,哪些是和策略模式没有任何关系的。


示例二:三国策略

 

1. 类图18-1

设计模式 c++版(13)——策略模式
 

2. 类图结构

  • IStrategy :策略类总称
  • BackDoor:乔国老开后门
  • GivenGreenLight:吴国太开绿灯
  • BlockEnemy:孙夫人断后

 

3.增加锦囊和执行人,类图18-2

设计模式 c++版(13)——策略模式


4.代码清单
 



//////////////////////    **********  2. 三国策略,代码清单18-2:***************//

//妙计接口
class IStrategy
{
public:
    virtual void    operate() = 0;
};

//乔国老开后门
class BackDoor: public IStrategy
{
public:
    virtual void    operate(){qDebug() << "BackDoor";}
};

//吴国太开绿灯
class GivenGreenLight: public IStrategy
{
public:
    virtual void    operate(){qDebug() << "GivenGreenLight";}
};

//孙夫人断后
class BlockEnemy: public IStrategy
{
public:
    virtual void    operate(){qDebug() << "BlockEnemy";}
};

//锦囊
class Context
{
public:
    Context(IStrategy* strategy)
    {
        this->m_strategy = strategy;
    }
    void    operate()
    {
        this->m_strategy->operate();
    }

private:
    IStrategy   *m_strategy;
};

//使用计谋
int main()
{
    IStrategy *backDoor      = new BackDoor();
    IStrategy *greenLight    = new GivenGreenLight();
    IStrategy *block         = new BlockEnemy();
    
    
    Context *context = new Context(backDoor);
    context->operate();
    
    context = new Context(greenLight);
    context->operate();
    
    context = new Context(block);
    context->operate();
    
    return 0;
}

 

三、建造者模式的应用

1. 优点

  •  算法可以*切换。这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封装角色对其进行封装,保证对外提供“可*切换”的策略。
  •  避免使用多重条件判断。多重条件语句不易维护,而且出错的概率大大增强。使用策略模式后,可以由其他模块决定采用何种策略,策略家族对外提供的访问接口就是封装类,简化了操作,同时避免了条件语句判断。
  •  扩展性良好。在现有的系统中增加一个策略很容易,只要实现接口就可以了,其他不用修改,类似于一个可反复拆卸的插件。

 

2. 缺点

  •  策略类数量增多。每一个策略都是一个类,复用的可能性很小,类数量增多。
  •  所有的策略类都需要对外暴露。上层模块必须知道有哪些策略,然后才能决定使用哪一个策略,这与迪米特法则相违背,这一缺点可以使用其他模式来修补,如工厂方法模式、代理模式或享元模式。


3. 使用场景:

  •  多个类只有在算法或行为上稍有不同的场景。
  •  算法需要*切换的场景。如算法的选择由使用者决定。
  •  需要屏蔽算法规则的场景。如太多的算法只要知道一个名字,传递相关的数字进来,反馈一个运算结果就可以。

 

4. 注意事项:

如果系统中的一个策略家族的具体策略数量超过4个,则需要考虑使用混合模式,解决策略类膨胀和对外暴露的问题,否则日后的系统维护就会很麻烦。

 

四、策略模式的扩展

1. 需求说明

输入3个参数,进行加减运算,参数中两个是int,一个是QString,只有“+”、“-”两个符号可以选择,不考虑校验

 

示例三:算数加减法(方案一)

2. 代码清单18-3
 

////////////////////////    **********  3. 算数加减法(方案一),代码清单18-3:***************//

class Calculator
{
public:
    int exec(int a, int b, QString symbol)
    {
        int result = 0;
        if (symbol == this->m_add)
        {
            result = this->add(a, b);
        }
        else if (symbol == this->m_sub)
        {
            result = this->sub(a, b);
        }
        return result;
    }
private:
    int     add(int a, int b){return a + b;}
    int     sub(int a, int b){return a - b;}
    
private:
    static QString  m_add;
    static QString  m_sub;
};

QString  Calculator::m_add = "+";
QString  Calculator::m_sub = "-";

int main()
{
    int a = 10;
    int b = 20;
    Calculator cal;
    qDebug() << cal.exec(a, b, "+");
    
    
    return 0;
}

 

 

示例四:算数加减法(方案二)

2. 代码清单18-3,( 简化Calculator )

////////////////////////    **********  4. 算数加减法(方案二),代码清单18-4:***************//


class Calculator
{
public:
    int exec(int a, int b, QString symbol)
    {
        return (symbol == m_add)? a+b:a-b; 
    }
private:
    int     add(int a, int b){return a + b;}
    int     sub(int a, int b){return a - b;}
    
private:
    static QString  m_add;
    static QString  m_sub;
};

QString  Calculator::m_add = "+";
QString  Calculator::m_sub = "-";

int main()
{
    int a = 10;
    int b = 20;
    Calculator cal;
    qDebug() << cal.exec(a, b, "+");
    
    
    return 0;
}

 

示例五:算数加减法(方案三)

2. 代码清单18-3,( 引入策略模式 )
 


//////////////    **********  5. 算数加减法(方案三:策略模式),代码清单18-5:***************//


class Calculator
{
public:
    virtual int exec(int a, int b) = 0;
};

class Add:public Calculator
{
public:
    virtual int exec(int a, int b)
    {
        return a + b;
    }
};

class Sub:public Calculator
{
public:
    virtual int exec(int a, int b)
    {
        return a - b;
    }
};

class Context
{
public:
    Context(Calculator  *cal)
    {
        this->m_cal = cal;
    }
    int exec(int a, int b)
    {
        return this->m_cal->exec(a, b);
    }

private:
    Calculator  *m_cal;
};

int main()
{
    int a = 10;
    int b = 20;
    Calculator *cal = new Add();
    Context context(cal);
    qDebug() << context.exec(a, b);
     
    return 0;
}

 


六、最佳实践

策略模式是一个非常简单的模式。他在项目中使用的非常多,但单独使用的地方比较少,因为有致命缺陷:所有的策略都需要暴露出去,这样才方便客户端决定使用哪一个策略。

 

 


参考文献《秦小波. 设计模式之禅》(第2版) (华章原创精品) 机械工业出版社