Effective C++ (E3 35)笔记之替代virtual函数的若干方案

假设要设计个游戏,里面不同类人物有不同生命值的计算方法:

那么基类定义一个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;

结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值