Strategy设计模式
什么式Strategy设计模式呢?字面意思来看,Strategy模式翻译过来就是策略模式。听到策略这个词我们自然会联想到计谋、计策之类的字眼。所以,策略模式就是一个面向计策的一个设计模式。在程序中,所谓计策就是算法。但是,话说回来,解决一个问题没有一个统一的算法,也没有一个算法可以解决全部的问题。那么我们的策略模式,自然也就不是一个解决全部问题的一种设计模式。但是,算法有一个特性,那就是它可以解决问题,这个是所有的算法的共同点,那么我们可以将这个共同点抽取出来,于是就有了我们的策略模式。
一个小demo开始
假如现在有一个问题摆在你眼前,让你去计算一个由字符串呈现的1+2-3*(4-1)
表达式的值。
解决这个问题,已经有了一个成熟的算法,那就是中缀表达式的求值算法(笔者在这里不在赘述算法的实现流程,不知道的可以自行百度学习)。而且我们也知道,中缀表达式是一个稳定的算法。其大致流程为:
虽然算法稳定,但是,算法的流程中有一个不太稳定的因素,那就是运算符。我们知道,我们不可能把所有的运算符都枚举在算法中,而且一旦所支持的运算符数目增多,那么我们的算法中将出现很多的if-else分支或者switch-case分支。这对于程序设计来说是非常不友好的,而且是非常不雅观的,而且,这个违背了软件设计的一个原则,那就是开闭原则——我们的软件对扩展是开放的,对修改是关闭的。
不妨我们假设有一个场景,突然有一天我们要增加支持一种新的运算符,我们要怎么做的。显然最直接的办法,就是多一层if-else判断,或者多一层switch-case语句。但是这显然与我们的开闭原则相违背。我们这样做是修改了源代码,而不是扩展了源代码。可能这句话听起来有点别扭,我添加了代码,为什么不是扩展。其实,这是因为,这里所谓的添加不能够做到编译单元的独立,所以这种行为称之为修改源代码。
那么,用strategy设置模式该怎么去书写呢?其实很简单,只要记住我们的面向对象设计原则一即可:高层模块不应该依赖于低层模块,二者皆应该依赖于抽象。
代码实现
首先我们定义一个运算符的抽象类
class OperatorStrategy{
public:
virtual ~OperatorStrategy(){};
virtual void run(Stack<float> *num)=0;
};
这个抽象类,他有一个抽象方法run
。这个抽象方法就是我们运算符对应的操作的实现。
然后,我们就可以定义我们的运算符操作类了。比如我们想支持加法运算操作
class AddStrategy
:public OperatorStrategy{
public:
void run(Stack<float> *num){
float n2 = num->pop();
float n1 = num->pop();
num->push(n1 + n2);
}
};
抑或是我们想支持减法操作
class SubtractStrategy
:public OperatorStrategy{
public:
void run(Stack<float> *num){
float n2 = num->pop();
float n1 = num->pop();
num->push(n1 - n2);
}
};
乘除操作自然也是可以
class MultiStrategy
:public OperatorStrategy{
public:
void run(Stack<float> *num){
float n2 = num->pop();
float n1 = num->pop();
num->push(n1 * n2);
}
};
class DivideStrategy
:public OperatorStrategy{
public:
void run(Stack<float> *num){
float n2 = num->pop();
float n1 = num->pop();
num->push(n1 / n2);
}
};
然后我们定义一个类工厂
class OperatorStrategyFactory{
private:
vector<OperatorStrategy*> operatorArray;
public:
OperatorStrategyFactory(){
operatorArray.push_back(new AddStrategy());
operatorArray.push_back(new SubtractStrategy());
operatorArray.push_back(new MultiStrategy());
operatorArray.push_back(new DivideStrategy());
}
OperatorStrategy* getStrategy(char symbol){
int index = getOperatorIndex(symbol);
return operatorArray[index];
}
};
这个类工厂在被实例出来之后,将会把其所支持的运算符操作加载到容器中。同时类工厂提供了一个getStrategy
的方法,用于我们获取相应的运算符操作类。注意,在这里我们的OperatorStrategy*
的指针是一个多态指针,他可指向任何继承自它的子类。
然后就是我们的中缀表达式的实现部分了
while( (symbol = expression[i]) ){
if( symbol >= '0' && symbol <= '9' ){
i += readNumber(expression, i,NUMBER);
continue;
}else if ( isOperator(symbol) ){
char stackTopSymbol = OPERATOR->getTopValue();
// get those symbol's index that at priority table
int t = getOperatorIndex(stackTopSymbol);
int n = getOperatorIndex(symbol);
/*
* action:
* 0 -> pop and calculate
* 1 -> push and next
* 2 -> pop and next
* -1 -> invalid
*/
int action = getAction(t, n);
if( action == 0 ){
char st = OPERATOR->pop();
OperatorStrategy* op = factory->getStrategy(st);
if( op ){
op->run(NUMBER);
continue;
}else{
printf("invalid expression\n");
break;
}
}else if( action == 1 ){
OPERATOR->push(symbol);
i++;
continue;
}else if( action == 2 ){
OPERATOR->pop();
i++;
continue;
}else if( action == -1 ){
printf("invalid expression\n");
break;
}
}else{
i++;
}
}
不难看到,在代码块
if( action == 0 ){
char st = OPERATOR->pop();
OperatorStrategy* op = factory->getStrategy(st);
if( op ){
op->run(NUMBER);
continue;
}else{
printf("invalid expression\n");
break;
}
}
我们发现通过借助OperatorStrategy*
这根多态指针,我们就可以将if-else分支简化为一行代码。而且,我们也实现了编译单元的独立,我们只需将所有OperatorStrategy的实现类包括它自身放到一个独立的文件中。这样当我们需要增添新的运算符时,只需要将运算符实现类所在文件编译即可,无需关注中缀表达式实现所在的文件。