怕什么真理无穷,进一寸有一寸的欢喜

设计模式之「策略」模式_设计模式


1.策略模式的动机

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

 

2.策略模式的定义

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

 

3.策略模式UML图

设计模式之「策略」模式_设计模式_02


(1) Context类:使用算法的角色,可以在解决不同问题时实例化出不同的具体策略对象。(2) Strategy类:抽象策略类,策略模式中提供一个抽象层来声明一个公共接口,抽象层的设计可以让Context类无差别的调用不同的具体策略方法。(3) ConcreteStrategy类:每种策略具体的实现方法。

设计模式之「策略」模式_设计模式_03


4.举个例子

我们都知道每个国家出租车的计费方式都不一样,因此针对不同国家我们应该使用不同的计费方法。下面我会使用对比传统方法与策略模式优化后的两种方法,首先使用传统方法,针对不同国家的情况当然是选择if-else大法啦。我们会很容易将代码写成下面这个样子:

/*  传统方法实现  */
enum TaxBase
{
    CN_Tax,
    US_Tax,
    DE_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***********
        }
        // ....
    }
};
但是,现在假设客户的需求变了,需要在原有实现的功能上增加法国出租车(FR_Tax)的计费方法。因此,只能硬着头皮对上面的代码进行修改,修改完后的代码如下所示。虽然我们实现了基本的需求,但违背面向对象设计原则中的开放封闭原则。即对已有代码进行直接修改是封闭的,而是在原有代码的基础上进行扩展是开放的。
/*  传统方法实现  */
enum TaxBase
{
    CN_Tax,
    US_Tax,
    DE_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***********
        }

        else if (tax == FR_Tax) // 增加法国出租车的计费方法
        {
            // FR***********
        }
        // ....
    }
};

 

使用策略模式来实现上面的需求,首先定义一个TaxStrategy抽象策略基类,具体策略类(如CNTax类)继承自抽象策略基类,然后在具体策略类中实现每个国家出租车具体的计费方法。最后,在SalesOrder类中调用同一个接口Calculate()就可以得到不同国家出租车的计算方法。现在,我们要增加一个新的国家的出租车计费方法,只需要新写一个类(如FRTax类),让它继承自TaxStrategy这个抽象类就可以啦。我们并不需要修改SalesOrder这个类,这也就是上面所说的开放封闭原则。

/*  策略模式实现  */
// 稳定
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;
    }

    double CalculateTax()
    {
        // ...
        Context context();

        double val = strategy->Calculate(context); // 多态调用
        // ...
    }
};

 

设计模式之「策略」模式_设计模式_04


5.策略模式实战

学过数据结构与算法的小伙伴们都知道,排序算法一大堆。面试官一问你,你肯定会说出一大堆,像什么冒泡排序、选择排序、归并排序、选择排序......诸如此类。不同的排序算法都有着不同的使用场合,因此我们就可以使用策略模式对各种排序算法进行封装,以便客户端可以针对不同场景更换不同排序算法。

#include <iostream>

using namespace std;

// 抽象策略类
class Strategy
{
public:
    Strategy() {}
    virtual void sort(int arr[], int N) = 0;
};

// 具体策略:冒泡排序
class BubbleSort : public Strategy
{
public:
    BubbleSort()
    {
        printf("冒泡排序\n");
    }
    void sort(int arr[], int N)
    {
        for (int i = 0; i < N; i++)
        {
            for (int j = 0; j < N - i - 1; j++)
            {
                if (arr[j] > arr[j + 1])
                {
                    int tmp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tmp;
                }
            }
        }
    }
};

// 具体策略:选择排序
class SelectionSort : public Strategy
{
public:
    SelectionSort()
    {
        printf("选择排序\n");
    }
    void sort(int arr[], int N)
    {
        int i, j, k;
        for (i = 0; i < N; i++)
        {
            k = i;
            for (j = i + 1; j < N; j++)
            {
                if (arr[j] < arr[k])
                {
                    k = j;
                }
            }
            int temp = arr[i];
            arr[i] = arr[k];
            arr[k] = temp;
        }
    }
};

// 具体策略:插入排序
class InsertSort : public Strategy
{
public:
    InsertSort()
    {
        printf("插入排序\n");
    }
    void sort(int arr[], int N)
    {
        int i, j;
        for (i = 1; i < N; i++)
        {
            for (j = i - 1; j >= 0; j--)
            {
                if (arr[i] > arr[j])
                {
                    break;
                }
            }
            int temp = arr[i];
            for (int k = i - 1; k > j; k--)
            {
                arr[k + 1] = arr[k];
            }
            arr[j + 1] = temp;
        }
    }
};

// 上下文类
class Context
{
public:
    Context()
    {
        arr = NULL;
        N = 0;
    }
    Context(int iArr[], int iN)
    {
        this->arr = iArr;
        this->N = iN;
    }
    void setSortStrategy(Strategy *iSortStrategy)
    {
        this->sortStrategy = iSortStrategy;
    }
    void sort()
    {
        this->sortStrategy->sort(arr, N);
        printf("输出: ");
        this->print();
    }
    void setInput(int iArr[], int iN)
    {
        this->arr = iArr;
        this->N = iN;
    }
    void print()
    {
        for (int i = 0; i < N; i++)
        {
            printf("%3d ", arr[i]);
        }
        printf("\n");
    }

private:
    Strategy *sortStrategy;
    int *arr;
    int N;
};

int main()
{
    Context *ctx = new Context();
    int arr[] = {10, 23, -1, 0, 300, 87, 28, 77, -32, 2};
    ctx->setInput(arr, sizeof(arr) / sizeof(int));
    printf("输入:");
    ctx->print();

    // 冒泡排序
    ctx->setSortStrategy(new BubbleSort());
    ctx->sort();

    // 选择排序
    ctx->setSortStrategy(new SelectionSort());
    ctx->sort();

    // 插入排序
    ctx->setSortStrategy(new InsertSort());
    ctx->sort();
    return 0;
}
上面程序的运行结果如下所示,从客户端的代码可以看出,客户端中无需关心各种排序算法的实现细节,所有的排序算法都是调用统一的sort()接口。如果要新增新的排序算法(如快速排序QuickSort),只需要在抽象策略基类SortStrategy中派生出一个新的QuickSort具体策略类就可以啦,无需对客户端程序进行修改,扩展起来方便。

设计模式之「策略」模式_设计模式_05


6.策略模式总结(1) Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法之间进行切换。(2) Strategy(策略)模式提供了用条件判断语句以外的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要用到Strategy(策略)模式。(3) 如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象开销。