假设要设计个游戏,里面不同类人物有不同生命值的计算方法:
那么基类定义一个impure virtual health函数,让派生类去同时继承它的接口和一份缺省实现,当然也可以重新定义。这是一个最常见而无甚新意的设计。
现有几种替代设计方案。
一、用NVI(non-virtual interface)手法实现template method
class chara{
public:
virtual int healthval() const;
};
class character1{
public:
int healthval() const{
cout<<"call healtval for NVI===>";
int retval=dohealthval();
return retval;
}
private:
virtual int dohealthval() const{
cout<<"call character1::dohealthval"<<endl;
}
};
class beggar1:public character1{
virtual int dohealthval() const{
cout<<"call beggar1::dohealthval"<<endl;
}
};
这种思想主张virtual应该总是private。令客户通过一个non-virtual成员函数间接调用private virtual函数,这个non-virtual成员函数是个外覆器。
NVI手法优点是base类可以在外覆器内做一些额外的事情,如加锁、验证约束条件等。这些由于事务已经被base类内做掉了,便没有了担心用户无法处理好这些事务的忧虑。
当然用户也可以重新定义private virtual,控制如何实现机能。而“外覆器”仅仅是控制在实现机能的基础上,附加了调用的时间点、注意事项而已。
使用:
character1* cp=new beggar1();
cp->healthval();
cp=new character1();
cp->healthval();
beggar1 xxxx;
xxxx.healthval();
结果:
二、用函数指针实现策略模式
class character2;
int default_health(const character2& gc);
class character2{
public:
typedef int (*healthcalfunc) (const character2&);
explicit character2(healthcalfunc hcf=default_health, int rawval=100)
:healthfunc(hcf),
rawhealthval(rawval)
{
}
int getrawhealthval() const{
return rawhealthval;
}
int healthval() const{
return healthfunc(*this);
}
void sethealthfunc(healthcalfunc hcf){
healthfunc=hcf;
}
private:
int rawhealthval;
healthcalfunc healthfunc;
};
int default_health(const character2& gc){
gc.getrawhealthval();
}
class badguy:public character2{
public:
explicit badguy(healthcalfunc hcf=default_health, int rawval=100, std::string str="")
:character2(hcf,rawval),
sname(str)
{
}
string& getname(){
return sname;
}
private:
std::string sname;
};
这种思想主张生命值计算方法应该和人物类型无关。计算生命值的实现单独作为一种策略,应该独立于人物,从而要求每个人物的构造函数都接受一个指向“生命值计算”函数的指针。
现在生命值计算策略和人物类关系松散了,类似于“private指针的复合关系”。因此提供了些许扩充性,如:
1. 同一人物的不同实体可以指定不同的生命值计算函数。
2. 都人物实例的生命值计算函数可以在运行期变更。如基类提供个sethealthfunc成员函数来重新设定生命值计算函数。
使用:
int glhealth(const character2& obj){
return obj.getrawhealthval()*1.2;
};
int sglhealth(const character2& obj){
return obj.getrawhealthval()*1.5;
};
int recoverlasthealth(const character2& obj){
return sglhealth(obj)+20;
};
std::string name="yugaoqiang";
badguy boss0(glhealth,100,name);
cout<<"name: "<<boss0.getname()<<endl;
cout<<"health val: "<<boss0.healthval()<<endl;
badguy boss1(sglhealth,100,"jiazhao");
cout<<"name: "<<boss1.getname()<<endl;
cout<<"health val: "<<boss1.healthval()<<endl;
boss1.sethealthfunc(recoverlasthealth);
cout<<"name: "<<boss1.getname()<<endl;
cout<<"health val: "<<boss1.healthval()<<endl;
结果:
此运用函数指针复合的手法替换virtual函数,其缺点也也显而易见:函数指针只是个成员变量,并非在人物继承体系内。要想访问人物类非public内部成员(如原始生命值),必须弱化人物类的封装。可以通过友元函数,实现一个public访问函数等。
增加策略的灵活性的同时弱化类的封装是否值得,这是值得考虑的。本文其余手法与此类似,也都值得注意。
三、使用std::function实现策略模式
和(二)的手法一样,只不过以std::function替代函数指针。因为std::function对象可以持有(保存)任何可调用物,包含函数指针、函数对象(仿函数)、成员函数指针等,只要其签名式兼容于需求端即可:
class character3;
int default_health1(const character3& gc);
class character3
{
public:
typedef std::function<int (const character3&)> healthcalfunc;
explicit character3(healthcalfunc hcf=default_health1, int rawval=100)
:healthfunc(hcf),
rawhealthval(rawval)
{
}
int getrawhealthval() const{
return rawhealthval;
}
int healthval() const{
return healthfunc(*this);
}
void sethealthfunc(healthcalfunc hcf){
healthfunc=hcf;
}
private:
int rawhealthval;
healthcalfunc healthfunc;
};
int default_health1(const character3& gc){
gc.getrawhealthval();
}
class evilbadguy:public character3{
public:
explicit evilbadguy(healthcalfunc hcf=default_health1, int rawval=100, std::string str="")
:character3(hcf,rawval),
sname(str)
{
}
string& getname(){
return sname;
}
private:
std::string sname;
};
使用:
short plhealth0(const character3& obj){
return obj.getrawhealthval()*1.8;
}
struct plhealth1{
int operator() (const character3& obj){
return obj.getrawhealthval()*1.9;
}
};
class gamelevel{
public:
float plhealth2(const character3& obj){
return obj.getrawhealthval()*1.91;
}
};
evilbadguy killer0(plhealth0,1000,"gangchen");
cout<<"name: "<<killer0.getname()<<endl;
cout<<"health val: "<<killer0.healthval()<<endl;
evilbadguy killer1(plhealth1(),1000,"jiabintang");//functor must use as functor()
cout<<"name: "<<killer1.getname()<<endl;
cout<<"health val: "<<killer1.healthval()<<endl;
cout<<"functor call: "<<plhealth1()(killer1)<<endl;//functot with para must be called as"functor()(xxxx)"
gamelevel curlevel;
auto func=std::bind(&gamelevel::plhealth2,&curlevel,placeholders::_1);
evilbadguy killer2(func,1000,"myself");
cout<<"name: "<<killer2.getname()<<endl;
cout<<"health val: "<<killer2.healthval()<<endl<<endl;
这里用了函数指针、仿函数和类的成员函数指针三种类型对象赋予std::function,都达到了调用目的。而作为类的(非static)成员函数指针,其第一个默认参数是this指针,因此它实际上有两个参数。而计算生命值的函数只接受单一参数,两者函数签名不符。
这里通过std::bind绑定参数,将第一参数指定为curlevel,返回一个单一参数的函数对象供类内部的std::function使用。
std::function带来的灵活性是函数指针无法比拟的,而且其行为就像一般的函数指针。
结果:
四、经典策略模式
class character4;
class healthcal{
public:
virtual int calc(const character4&) const=0;
};
class character4{
public:
explicit character4(healthcal* phc, int rawval=100)
:ptr_hc(phc),
rawhealthval(rawval)
{
}
int getrawhealthval() const{
return rawhealthval;
}
int healthval() const{
return ptr_hc->calc(*this);
}
void sethealthfunc(healthcal* phc){
ptr_hc=phc;
}
virtual string& getname(){
string aa;
return aa;
}
private:
int rawhealthval;
healthcal* ptr_hc;
};
class char4_guy0:public character4{
public:
explicit char4_guy0(healthcal* phc=NULL, int rawval=100, std::string str="")
:character4(phc,rawval),
sname(str)
{
}
string& getname(){
return sname;
}
private:
std::string sname;
};
class badguy_healthcal:public healthcal{
public:
virtual int calc(const character4& gc) const{
return gc.getrawhealthval()*1.5;
}
};
class candyguy_healthcal:public healthcal{
public:
virtual int calc(const character4& gc) const{
return gc.getrawhealthval()*0.7;
}
};
同样是将策略独立于出来,这次是将策略作为一个单独的类而不是函数指针来实现。
同样是通过复合,不过这次策略类可以被子类继承,人物类中有一策略类的指针,指向其策略类的继承体系(healthcal基类)。
使用:
healthcal* funcptr=new badguy_healthcal();
character4* objptr=new char4_guy0(funcptr,10,"cplspls");
cout<<"name: "<<objptr->getname()<<endl;
cout<<"health val: "<<objptr->healthval()<<endl;
结果: