2023年12月9日发(作者:荣耀10青春版跑分)
泛化策略模式(Strategy)
泛化策略模式简述
在上一章的泛化对象工厂中我们知道怎么将一系列带有继承关系的类映射到字符串上,这让我们的代码可以独立于客户端改变行为,但
是有时候泛化对象工厂又现得有点捉襟见肘,也许我们想要替换的的行为并没有继承关系,这时候我们就必须想到另外一种设计模式来解决
我们的问题。
在【GoF】中描述的策略模式正是我们需要的:“定义一系列算法,把它们一个个封装起来,并且使他们可以互相替换。使得算法可以独
立于使用它的客户而变化。”其中并没有提到这些算法必须有什么关系。
其实在【GoF】中将这“一系列算法”都继承自一个基类。不过对于C++,我们能够做得更好,可以让这“一系列算法”都无所有有任何联
系。【GoF】中一个例子是我们对文本编辑器的换行操作运用到策略模式就可以针对不同的需要改变不同的策略,并且在增加算法时候不用
修改客户端代码。
8.1.1 定义
定义一系列算法,且这些算法没有直接(继承)关系,把它们存储起来,并且使它们可以互相替换。本模式使得算法可独立于使用它的
客户端而变化。
8.1.2 泛化目的
对于传统的策略模式仍然有继承关系的局限,没有完全达到策略模式的定义,泛化的目的是总希望减少算法之间的耦合,对算法之间的
关系没有苛刻的要求,最大程度的解放程序员。
8.1.3 适用场合
下列场合建议使用泛化策略模式:
对于一个算法,我们希望其根据环境的不同而进行改变。
算法使用客户不需要知道具体行为,而只需要在不同需要时进行所需替换。
当你的程序中出现落后且难于维护的条件判断语句以执行不同算法时。条件判断语句正是面向对象多态想避免的东西,其难于维护,
当每添加一个算法(行为)时候总是需要修改客户端代码。
8.1.4 实作泛化策略模式前的思考
C++之所以迷人就是因为其无所不能的灵活,C++从来都在告诉所有藐视其语言庞杂性的程序员它是无所不能的。在我程序设计过程中
遇到过很多连工作了多年的其他语言朋友也感觉不可能的解决的问题,例如用一个容器存储不同数据类型、用一个容器存储不同数据结构,
不过只要你有不放弃的精神,就总能在C++中找到解决的方法,至少C++可以做到很多看似神奇的工作。
当若干算法(行为)不具有继承关系时候我们将怎么存储是一个难题,前面的对象工厂里面介绍时候的第一个实作品是一个存储对象的
容器,但是必须是这些对象(算法,行为)必须具有继承自相同的基类的特点。如果所有行为没有绝对的关系,那我们将无法用一个类似链
表或者关联容器去存储,这就强迫我们必须自己设计解决算法。
最好的办法,也是我们用了很多次的办法就是利用多态中基类对继承类新成员的“一无所知”解决问题。思考解决问题的方法是需要一定
得程序经验积累,需要大脑的不断思考。下面我们开始看看怎么存储不同类型的行为算法。
8.1.5 行为算法的定义
在C++中通常是函数来代表一个行为算法,不过由于函数作为参数将带来极大地安全隐患(函数指针不具有value语义),所以在我这的
算法是希望用泛化仿函数作为行为算法的载体,当然泛化仿函数有一个好处就是同样兼容函数指针(详见第五章)。
8.1.6 实作行为容器
首先我们需要创建三个类:
BaseStrategy(虚基类,定义行为调用函数)
Strategy(继承自BaseStrategy,定义了一个泛化对象工厂以存储其继承类,这句话会比较难理解,请看后面代码)
ConcreteStrategy(继承自Strategy,存储行为)
这样的结构好处就是Stratrgy对ConcreteStrategy中存储的行为可能一无
所知,但是却能通过虚函数得到。BaseStrategy的存在完全是为了应付行为参数不同的重载(可参考第五章泛化仿函数实作)。
下面看看具体代码,需要声明的是出于控制文章长度考虑,我假设行为参数至多只有两个,如果再添加更多参数只需要拷贝修改前面的代
码。需要注意的是对算法的映射我依然采用算法名字的字符串映射。
//和往常一样,ResultType为返回值类型,TVector为参数类型
template
class BaseStrategy
{
};
//下面这是对没有参数的行为的偏特化体
template
class BaseStrategy
{
public:
//下面的就是行为算法的调用函数。
//需要解释的是由于定义纯虚函数就不能作为模板参数,所以这里只能被迫随便返回一个临时值,不//过不用担心的是这个临时值肯定不会被真正返回,只是为了通过编
译。
virtual ResultType AlgorithmInterface(){return ResultType();}
};
template<>
class BaseStrategy
{
public:
//由于当返回值为void时候return void()是不能通过编译的,所以我这里也只能被迫做一个void返//回值的偏特化版本。
virtual void AlgorithmInterface(){};
};
//下面这个是对一个参数的偏特化体
template
class BaseStrategy
{
public:
virtual ResultType AlgorithmInterface(p1){return ResultType();}
};
template
class BaseStrategy
{
public:
virtual void AlgorithmInterface(p1){};
};
//下面这是对两个参数的偏特化体
template
class BaseStrategy
{
public:
virtual ResultType AlgorithmInterface(p1,p2){return ResultType();}
};
template
{
public:
virtual void AlgorithmInterface(p1,p2){};
};
上面的程序可以根据不同的参数个数特化出不同的代码。下面是Strategy代码:
template
class Strategy:public BaseStrategy
{
public:
//这里将用到泛化对象容器(详见第七章泛化对象工厂第一个实作品)存储其本身的基//类,之所以用泛化对象容器而不用泛化对象工厂的原因是我们可能需要在不同次
调用//的时候希望调用的是同一个泛化仿函数对象,这样的做法有点微妙,不过很不错。
ObjectFactories
//这里是插入的最后一个子类的对象地址
template
void addStrategy(char *temp)
{
(temp,new ConcreteStrategy
}
};
接下来将是ConcreteStrategy的代码:
template
class ConcreteStrategy : public Strategy
{
//这里存储行为
Functor fun;
public:
//下面是对参数的类型的提取
typedef typename TypeAt
typedef typename TypeAt
//接下来就是对不同参数个数行为的重载体
ResultType AlgorithmInterface()
{
return fun();
}
ResultType AlgorithmInterface(Parm1 p1)
{
return fun(p1);
}
ResultType AlgorithmInterface(Parm1 p1,Parm2 p2)
{
return fun(p1,p2);
}
};
这样我们的容器就完成了,其中真正的存储位置是Strategy类中的ObjectFactories,但是ObjectFactories也无法知道后面的子类中存储的
行为,但是却可以通过多态的虚函数来取得行为。
这一切的获得也都是一种似乎顺理成章的思维—反复利用多态,所以在此我也无法过多阐述。
8.1.7 实作策略模式
策略模式的定义告诉我们必须有一个容器能够动态的存储算法并且随时根据某个索引条件提取算法,但是这一切都必须不影响客户端的代
码。
容器设计应该是整个泛化策略模式的难点,不过我们在前面已经通过运用多态存储其自身的对象工厂以实现了存储任何算法了,我们现在
需要的就是对这个容器进行封装,提供一系列运行期的算法以支持客户动态的维护容器。在【GoF】中是一个Context类对容器进行管理,
我也将实作这个类,但是出于泛化目的,和【GoF】中的Context有很大不同,下面来看看我的代码。
template
class Context
{
//这个是我们先前实作的可以存储任何算法的容器
//但其实真正的容器是在这个strategy的继承类内部,很妙的程序,请看后面
BaseStrategy
public:
//再次申明,为了缩短篇幅,我假设存储的算法至多有两个参数,但不代表不能做更多。
typedef typename TypeAt
typedef typename TypeAt
//构造函数,这个是构造一个空对象
Context()
{
}
//构造函数,这个构造函数接收2个参数,允许用户在构造的时候就初始化算法或者在后面的ReInit
//时候修改
Context(char *StrategyName,Strategy
{
strategy = temp->Object(StrategyName);
}
//这个函数用于修改算法,第一个参数是算法的关联名字,第二个参数是算法的存储容器
bool ReInit(char *StrategyName,Strategy
{
BaseStrategy
_tmp = temp->Object(StrategyName);
if(_tmp != 0)
{
//初始化成员变量
strategy = _tmp;
return 1;
}
else
return 0;
}
//以下是调用算法的不同参数个数的重载体。
ResultType ContextInterface() {
return strategy->AlgorithmInterface();
}
ResultType ContextInterface(Parm1 p1)
{
return strategy->AlgorithmInterface(p1);
}
ResultType ContextInterface(Parm1 p1,Parm2 p2)
{
return strategy->AlgorithmInterface(p1,p2);
}
};
8.1.8 使用示例
到此我们的泛化策略模式就实作完成了,让我们来看看我们的成果:
//这是行为算法fun1
struct fun1
{
int operator () (int i)
{
std::cout<<"The fun1 has be run"< return 1; } }; //这是行为算法2,需要注意的是fun1和fun2没有直接联系 struct fun2 { int operator () (int i) { std::cout<<"The fun2 has be run"< return 1; } }; int main() { //这里是定义一个算法容器,第一个模板参数是返回值类型,第二个模板参数是算法参数类型 Strategy //把需要的算法加入进来 //其中模板参数是算法本身,函数参数是算法映射的字符串名字 ategy ategy //生成一个策略 //并且指向fac容器中的fun1算法, //第一个模板参数是返回值类型,第二个模板参数是算法参数类型 Context //执行算法,关键是这里将会出现在客户端,而客户端代码没有携带算法的信息 tInterface(2); //改变策略指向fac容器中的fun2算法 ("fun2",&fac); //执行算法,这时候这一句和先前的执行算法看上去没有差异 tInterface(1); getchar(); return 0; } 8.1.9 效果总结 消除了条件判断语句。泛化策略模式给了一种运行期动态维护算法的方法,这使得你不用为了达到类似的目的而使用许多if或者switch 语句,这在一定程度上提高了代码的阅读性和维护性。 实现选择。 泛化设计模式提供了相同行为不同实现的方法,客户可以根据不同的环境权衡取舍要求从不同策略中进行选择。 客户必须了解不同的Strategy。在代码中客户如果想要改变一个策略就必须了解这个策略的更多信息,比如算法名字,算法行为。 泛化策略模式是将没有直接关系的一系列算法联系在一起并且可以相互替换。 这一系列算法必须具有相同的返回值类型和参数个数以及参数类型。 泛化策略模式核心是利用多态中基类对基础类的新成员变量一无所知来实现不同类型的算法的存储。
发布者:admin,转转请注明出处:http://www.yc00.com/num/1702054891a1171500.html
评论列表(0条)