设计模式之策略模式

2018-09-21 15:54:34

策略模式

  工厂系列模式只是解决了对象创建的问题。策略(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。算法本身是一种策略,而且这种策略是随时都可能相互替换的,这就是变化点,而封装变化点是面向对象的一种很重要的思维方式。策略模式是一种定义一系列算法的方式,从概念上卡,所有这些算范完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。

UML类图

  Context:上下文,用一个ConcreteStrategy来配置,持有一个Strategy的引用,最终交给客户端代码使用

  Strategy:策略类,定义所有支持的算法的公共接口

  ConcreteStrategy:具体策略类,封装了具体的算法或行为,继承于Strategy。

策略模式的优缺点

优点:

  1.策略模式的Strategy类层次为Context定义了一些列的可供重用的算法或行为。继承有助于析取出这些算法的公共功能。

  2.简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。

  3.当不同的行为堆砌在一个类中时,很难避免使用条件语句来选择合适的行为。比如你需要在客户端代码中来进行选择使用哪一种算法。将这些行为封装在一个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。策略模式就是用来封装算法的,但在实践中,我们发现可以用它来分装几乎任何类型的规则,只要在分析过程中听到需要在不同实践应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

缺点:

  1.在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象,这本身并没有解除客户端需要选择判断的压力(也就是说客户端需要知道所有的策略类型)。但是可以通过工厂模式进行一定程度的改进。

  2.但策略类增加时,将可能产生大量的子类,如果一个算法被多次使用,但仅是外部条件不同的时候,可以考虑使用享元模式来进行优化,减少实例的个数,但这在涉及线程安全的时候需要格外注意。

适用场景:

  1.需要在不同的时间应用不同的业务规则时可以考虑使用策略模式,以后规则还有可能增加,且每种规则都可以抽象成独立的类

  2.对客户端隐藏实现算法的细节,是客户端和算法之间完全独立

代码示例

  超市打折策略:正常收费、消费满指定金额优惠一定金额、消费满指定金额给予一定的折扣。

1.所有策略的基类(UML类图中的Strategy)

#ifndef STRATEGY_H_
#define STRATEGY_H_

class Strategy
{
public:
    virtual double acceptCash(const double iOriginal) const = 0;
    Strategy() = default;
    virtual ~Strategy() = default;
};
#endif
Strategy

2.超市的三种打折策略(UML类图中的ConcreteStrategy)

#ifndef CASHNORMAL_H_
#define CASHNORMAL_H_

#include "Strategy.h"

class CashNormal : public Strategy
{
public:
    double acceptCash(const double dOriginalCash) const override
    {
    return dOriginalCash;
    }
    CashNormal() = default;
    ~CashNormal() = default;
};
#endif


#ifndef CASHREBATE_H_
#define CASHREBATE_H_

#include "Strategy.h"

class CashRebate : public Strategy
{
public:
    double acceptCash(const double iOriginalCash) const
    {
    if(iOriginalCash > m_dCondition)
        return iOriginalCash * m_dRebate; 
    return iOriginalCash;
    }
    CashRebate(double dCondition,double dRebate):m_dCondition(dCondition),m_dRebate(dRebate){};
    ~CashRebate() = default;
private:
    double m_dCondition{0};        //if cash > m_dCondition,you can have a discount
    double m_dRebate{1};        //discount as m_dRebate
};
#endif

#ifndef CASHRETURN_H_
#define CASHRETURN_H_
#include "Strategy.h"

class CashReturn : public Strategy
{
public:
    double acceptCash(double dOriginalCash) const override
    {
    if(dOriginalCash > m_dCondition)
        return dOriginalCash - dOriginalCash/m_dCondition*m_dReturn;
    return dOriginalCash;
    }
    CashReturn(const double dCondition,const double dReturn): m_dCondition(dCondition), m_dReturn(dReturn){}
    ~CashReturn() = default;
private:
    double m_dCondition{1};
    double m_dReturn{0};
};
#endif
ConcreteStrategy

3.上下文类(负责和客户代码打交道,UML类图中的Context)

#ifndef CONTEXT_H_
#define CONTEXT_H_

#include "Strategy.h"

class Context
{
public:
    double contextInterface(double dOriginalCash);
    Context(Strategy * pConcreteStrategy)
    {
    m_pStrategy = pConcreteStrategy;
    }
    ~Context()
    {
    }
private:
    Strategy* m_pStrategy{nullptr};
};
#endif 

#include "Context.h"

double Context::contextInterface(double dOriginalCash)
{
    if(nullptr == m_pStrategy)
    return 0;
    return m_pStrategy->acceptCash(dOriginalCash);
}
Context

4.Client

#include "Context.h"
#include "CashNormal.h"
#include "CashRebate.h"
#include "CashReturn.h"
#include <iostream>
#include <string>
using namespace std;
const std::string RETURN = "return";
const std::string REBATE = "rebate";
const std::string NORMAL = "normal"; 
int main(int argc,char *argv[])
{
    if(argc != 2)
    {
    cout << "Param count is error!" <<endl;
    }
    string strCommand = argv[1];
    if(strCommand == RETURN)
    {
    CashReturn objCashReturn(300,100);
        Context objCashContext(&objCashReturn);
        cout << "Amount receivable :" << objCashContext.contextInterface(400);
    }
    else if(strCommand == REBATE)
    {
    CashRebate objCashRebate(300,0.8);
        Context objCashContext(&objCashRebate);
        cout << "Amount receivable :"<< objCashContext.contextInterface(350);
    }
    else if(strCommand == NORMAL)
    {
    CashNormal objCashNormal;
        Context objCashContext(&objCashNormal);
        cout << "Amount receivable :" << objCashContext.contextInterface(500);
    }
    else
        cout << "Command is Error " << endl;
    return(1);
}
Client

  总结:其实从这个小例子能看出来策略模式的最大优点是通过Context类隔绝了策略的具体实现和客户端代码的关系,并有利于横向扩展,但这里的问题是,客户代码必须要知道每一个策略类叫什么名字,才能正确的使用这个策略。优化的时候可以考虑使用工厂模式,但使用工厂模式的话,必须要考虑,怎样把策略需要满足的条件传递过去,使得整个模式能够正常的工作。

  

原文地址:https://www.cnblogs.com/ToBeExpert/p/9687944.html