设计模式 II ——策略模式(Strategy)
问题概述
在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们需要根据环境或者条件的不同选择不同的算法或者策略来完成该功能。
一种常用的方法是编码在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法,但是问题在于,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。
示例:简单计算器的设计,设计的计算器具有进行加、减、乘、除运算的功能,并且可根据需要添加新的运算方法(比如MOD)。
对于一个小学生来说,开发一个具简单四则运算的计算器就足以应付大多数的情况了,但是当需要计算余数时,如果要添加MOD的计算功能就需要对整个计算器源码进行修改并重新编译,这是我们不想见到的情况。
需求分析
将对象和策略分开,使得对象可以独立于策略而存在,策略的变化可以独立于用户?
解决方案
策略模式(Strategy):定义一系列的算法类,分别封装,使得他们之间可以相互替换,让算法变化而不影响到用户。
UML类图
模式组成
环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。
具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。
优劣分析
优点
- 简化了单元测试,每个算法都有自己的类,可以通过自己的接口单独测试
- 为继承类的替换添加提供了简便的处理方法
缺点
- 需要客户端作出判断,客户必须要知道所有的策略类的具体实现,并自行决定调用哪个策略类
- 环境类(Context)与抽象策略类(Strategy)间的通讯增加时间成本
- 对与策略较多的问题,具体策略类的数量过大会使得整体冗余
- 内存管理工作需要由客户完成
示例实现
#ifndef CALCULATOR_H_
#define CALCULATOR_H_
//抽象策略类(Strategy)
class Coperation
{
public:
double _nFirst;
double _nSecond;
virtual double GetResult()
{
double dResult = 0.0;
return dResult;
}
};
//具体策略类(ConcreteStrategy)
//加法
class AddOperation :public Coperation
{
public:
AddOperation(double a, double b)
{
_nFirst = a;
_nSecond = b;
}
virtual double GetResult()
{
return _nFirst + _nSecond;
}
};
//减法
class SubOperation :public Coperation
{
public:
SubOperation(double a, double b)
{
_nFirst = a;
_nSecond = b;
}
virtual double GetResult()
{
return _nFirst - _nSecond;
}
};
//乘法
class MulOperation:public Coperation
{
public:
MulOperation(double a, double b)
{
_nFirst = a;
_nSecond = b;
}
virtual double GetResult()
{
return _nFirst*_nSecond;
}
};
//除法
class DivOperation :public Coperation
{
public:
DivOperation(double a, double b)
{
_nFirst = a;
_nSecond = b;
}
virtual double GetResult()
{
return _nFirst / _nSecond;
}
};
//环境类(Context)
class Context
{
private:
Coperation *op;
public:
Context(Coperation *temp)
{
op = temp;
}
double GetResult()
{
return op->GetResult();
}
~Context()
{
delete op;
}
};
#endif
由于环境类只维护一个对于Strategy对象的引用,通过返回一个指向具体策略类的抽象策略类指针来进行构造,因此具体策略类的构造和析构工作只能交给客户端来完成。
#include <iostream>
#include "calculator.h"
//客户端
int main()
{
using namespace std;
double a, b;
cout << "please input two numbers: ";
cin >> a >> b;
cout << "please input the type('+' '-' '*' '/'): ";
char c;
cin >> c;
switch (c)
{
case '+':
{
Context *contextAdd = new Context(new AddOperation(a, b));
cout << contextAdd->GetResult() << endl;
delete contextAdd;
break;
}
case '-':
{
Context *contextSub = new Context(new SubOperation(a, b));
cout << contextSub->GetResult() << endl;
delete contextSub;
break;
}
case '*':
{
Context *contextMul = new Context(new MulOperation(a, b));
cout << contextMul->GetResult() << endl;
delete contextMul;
break;
}
case '/':
{
Context *contextDiv = new Context(new DivOperation(a, b));
cout << contextDiv->GetResult() << endl;
delete contextDiv;
break;
}
default:
break;
}
cin.sync();
cin.get();
return 0;
}
使用场景
适合类中的成员以方法为主的情况,算法经常变动