《Effective C++》读书笔记 条款35:考虑virtual函数以外的其他选择

其实这一条款没有特别理解,以下就是一些浅显的记录。
virtual函数大家应该都熟悉,将成员函数写成virtual,是为了让派生类能够继承其接口和缺省的实现,如果派生类有自己的实现方式,就重写virtual函数,如果不需要自己特别的实现方式,就继承base class缺省的实现方式即可。常规思维大家在实现类的设计的时候只要涉及到继承,这种需要每个派生类不同的函数,比如对于鸟这种基类,鸟会叫,但是每种鸟叫的不一样,一般的想法都是将叫写成virtual函数,以便每一种具体的鸟有不同的叫声,但是本条款给大家提供了不同的实现方式,让在设计类时有更多的方式。
1.借由Non_Virtual Interface手法实现Template Method模式
Non_Virtual Interface简称NVI,是让用户通过public non_virtual成员函数调用private virtual函数实现和public virtual函数一样的功能。比如设计一个游戏,游戏中的每个人物都有一个健康状态值,一般的方式是设计一个基类,将健康状态计算函数设计成virtual函数,如下:

class GameCharacter
{
public:
	virtual int healthValue() const; //返回人物的健康指数,派生类可重新定义它
};

但是NVI方式可如下设计:

class GameCharacter
{
public:
	int healthValue() const //派生类继承它的接口和实现
	{
		//事前工作
		//...........

		int realVal = doHealthValue();

		//事后工作
		//...........

		return realVal;
	}
private:
	virtual	int doHealthValue()const;//派生类可重新定义它
};
//这里 non_virtual函数healthValue()称为virtual 函数doHealthValue()的外覆器

在这种设计中,我们将计算健康值的函数设计成了普通的成员函数,而具体的计算方式设计成private virtual函数。所有继承它的派生类会直接继承healthValue()函数的接口和实现,派生类可以根据自己的情况改写virtual函数。它的优点是可以在调用虚函数之前做一些事前准备工作,在虚函数调用之后做一些事后处理工作。但是并不是所有的情况都能适用,比如析构函数必须是public virtual ,就不能实施NVI手法。
2.借由Function Pointers实现Strategy模式
函数指针大家应该都不陌生,这种设计模式就是利用函数指针,使在设计的时候要实现的函数可以完全脱离具体的派生类,给基类的构造函数传递一个函数指针,指向我们即将要实现的功能,对于不同的派生类,可以根据需要定义不同的实现函数,只要返回类型函数参数一样,就可以作为函数指针的实参传入,具体的计算函数可以定义成非成员函数,如下所示:

class GameCharacter;//前置声明
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter
{
public:
	typedef int(*HealthCalcFunc)(const GameCharacter&);
	explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) :healthFunc(hcf)
	{

	}
	int healValue()const
	{
		return healthFunc(*this);
	}
private:
	HealthCalcFunc healthFunc;
};

这种设计模式使得设计派生类时可以根据具体情况设计自己的计算函数,将计算函数作为指针传给基类构造函数,优点是:同一派生了不同实体之间可以有不同的计算函数;在运行期可以变更计算函数。如果计算函数没有用到类的非public成员那么这种设计没有问题,但是如果在计算的过程中需要访问类的非public成员,可以将函数声明为friend,这种做法会降低类的封装性。
3.借由tr1::function完成Strategy模式
这次不用函数指针。而是用一个类型为tr1::function的对象,这种方式使得在第二种方式的基础上使得计算过程不一定是个函数,而是某种像函数的东西,返回类型可以是任何可与声明式中的返回类型互相转换的类型,参数类型可以是能互相转换的类型。

class GameCharacter
{
public:
	typedef std::tr1::function<int (const GameCharacter&) > HealthCalFunc;
	explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) :healthFunc(hcf)
	{

	}
	int healValue()const
	{
		return healthFunc(*this);
	}
private:
	HealthCalcFunc healthFunc;
};

如上的typedef的声明,HealthCalcFunc可以是任何可调用的并接受任何兼容于GameCharacter的参数,返回任何兼容于int的东西。这使得在设计派生类时在指定计算函数时有很大的弹性,只要能转换成我们需要的形式就都可以传入基类的构造函数。
比如下面的调用就是正确的

short calcHealth(const GameCharacter&);
class EvilBadGuy :public GameCharacter
{

};

EvilBadGuy ebg1(calcHealth); //使用返回类型是short的计算函数计算健康指数

struct HealthCalcultor
{
	int operator()(const GameCharacter&)const
	{

	}
};

EvilBadGuy ebg2(HealthCalcultor()); //使用函数对象计算健康指数,该Healculcalcutor()是可调用的接受      
                                                                   //GamerCharacter并返回int的,所以合理                        
class GameLevel
{
public:
	float Health(const GameCharacter&)const
	{

	}
};
EvilBadGuy ebg3(std::tr1::bind(&GameLevel::Health, currentLevel, _1));//使用某个成员函数计算健康指数

4.古典的Strategy模式

这种模式用类替换函数指针,将计算函数写成一个类,将计算过程写成virtual函数,在我们要设计的继承体系中,将计算函数类的指针传给构造函数,与第二条类似,只是将函数指针换成了类

class GameCharacter;//前置声明
class HealthCalcFunc
{
public:
	virtual int calc(const GameCharacter& gc)const
	{

	}
};
HealthCalcFunc defaultHealthCalc;

class GameCharacter
{
public:
	typedef int(*HealthCalcFunc)(const GameCharacter&);
	explicit GameCharacter(HealthCalcFunc* phcf = &defaultHealthCalc) :pHealthCalc(phcf)
	{

	}
	int healValue()const
	{
		return pHealthCalc->calc(*this);
	}
private:
	HealthCalcFunc* pHealthCalc;
};

首先,计算函数类的计算过程函数是虚函数,保证了可以根据具体情况继续设计派生类的计算过程,再看上述代码,成员变量是一个指向计算函数对象的指针,这样就能保证将计算函数的派生类传递给计算函数基类指针,如果我们设计了一个游戏人物的派生类,它有自己的计算健康指数的计算过程,我们就可以将该计算过程设计成计算函数基类的派生类,改写计算过程为该人物需要的计算过程,然后就可以在构造该人物对象的时候将派生类计算对象传入人物的构造函数,在计算该人物的健康指数时,根据多态性,调用的是派生类计算函数的计算过程。

class GameCharacter;//前置声明
class HealthCalcFunc
{
public:
	virtual int calc(const GameCharacter& gc)const
	{

	}
};
HealthCalcFunc defaultHealthCalc;

class GameCharacter
{
public:
	typedef int(*HealthCalcFunc)(const GameCharacter&);
	explicit GameCharacter(HealthCalcFunc* phcf = &defaultHealthCalc) :pHealthCalc(phcf)
	{

	}
	int healValue()const
	{
		return pHealthCalc->calc(*this);
	}
private:
	HealthCalcFunc* pHealthCalc;
};
class DeriverdHealthCalcFunc :public HealthCalcFunc
{
public:
	int calc(const GameCharacter& gc)const
	{
		//不同于基类的计算过程
	}
};

class EvilBadGuy :public GameCharacter
{
public:
explicit EvilBadGuy(HealthCalcFunc* phcf = &defaultHealthCalc)
:GameCharacter(phcf )
{
}
};
DeriverdHealthCalcFunc deriverdfun;
EvilBadGuy badguy(&deriverdfun);
int realvalue=badguy.healValue();//根据多态性调用派生类计算函数,

请记住:
1.使用non_virtual interface(NVI)手法,那是Template Method设计模式的一种特殊形式。它以public non_virtual成员函数包裹较低访问性的(private或protected)virtual函数。
2.将virtual函数替换为“函数指针成员变量”,这是strategy设计模式的一种分解形式。
3.以tr1::function成员变量替换virtual函数,因而允许使用任何可调用物搭配一个兼容于需求的签名式。这也是strategy设计模式的某种形式
4.将继承体系内的virtual函数替换成为另一个继承体系内的virtual函数。这是strategy设计模式的传统实现手法。
这一章理解的比较浅显,在写的过程中也没有做出比书上更易懂的解释,但是写完这篇加深的自己的记忆,也算是有收获的。

猜你喜欢

转载自blog.csdn.net/junya_zhang/article/details/83513502
今日推荐