您的位置:首页 > 其它

条款35 考虑virtual函数以外的其他选择(virtual函数的替代方案)

2014-08-30 17:26 423 查看
virtual函数的替代方案有以下几种:

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设计模式的传统实现手法。

以下依次举例说明上述方法:

假设最初的类游戏人物基类GameCharacter,有一个virtual函数来计算健康值的计算heathValue(),继承类可以继承该类, 对于不同的继承类有不同的计算健康值的方法,所以各个类重写heathValue函数就可以实现各自的方法了。如下:

//游戏人物的基类,会有不同的继承类
class GameCharacter
{
public:
//返回人物健康指数,不是纯虚,说明是有一个计算健康指数的缺省算法
virtual int healthValue() const;
};
以上是基本问题,以下说明各个替代方案:

1 NVI手法(non-virtual interface):令客户端调用public non-virtual成员函数间接调用private virtual函数。是模板方法点击打开链接(偶总结的模板方法链接呀)
的一个特殊形式,模板方法是在基类中定义一个模板方法,这个方法规定了算法的固定步骤,然后可以再定义原语操作,可以为pure virtual表明必须由子类实现,这样针对不同的类,这个模板方法有不同的表现形式。NVI在该例的使用形式就是保留healthValue为public成员函数,但它成为non-virtual,并调用一个private virtual函数(doHealthValue)进行实际函数。这个non-virtual函数,healthValue成为virtual函数的外覆器,如下:

//方案1:NVI手法
class GameCharacter
{
public:
int healthValue() const
{
//可以做一些事前工作,如包括锁定互斥器,制造运转日志记录,验证classu
//约束条件,验证函数先决条件等等
...
int retVal = doHealthValue();
...
//调用结束后还可以做一些事后工作,包括互斥器接触锁定,验证函数的事后
//条件,再次验证类约束条件
return retVal;
}
private:
virtual int doHealthValue() const   //继承类可以重定义它
{
...//缺省算法,计算健康指数
}
};
这个手法的优点是可以做一些事前工作和事后工作。手法使继承类重写定义private virtual函数,而且这个函数并不被基类调用(因为是私有的)。但重新定义virtual表示某些事“如何”被完成,调用"virtual函数"则表示何时被完成。NVI手法允许derived class重新定义virtual函数,从而赋予它们“如何被实现”的控制能力,但是base class则述说“函数何时被调用”的权利。这个虚函数不一定必须是private,可能在某些情况下是protected或public.但这个手法其实还是用virtual函数来计算健康指数。

2 函数指针实现strategy模式:人物健康指数计算与人物类型无关,这个函数不再是GameCharactor继承体系内的成员函数,单独分离出去,而人物的构造函数接受一个指针,指向一个健康计算函数,这个函数来进行实际的计算,如下:

//2 成员函数指针实现stategy模式
class GameCharactor;  //前置声明
//计算健康指数的缺省算法
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter
{
public:
//定义函数类型
typedef int (*HealthCalcFunc)(const GameCharacter&);
explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc)
: HealthFunc(hcf)
{}

int healthValue() const
{
return healthFunc(*this);
}
private:
HealthCalcFunc heathFunc;
};
//GameCharacter可提供一个成员函数setHealthCalculator,
//用来替换当前的健康指数计算函数,则函数可以在运行时变更

//同一人物类型的不同实体可以有不同的健康计算函数
class EvilBadGuy : public GameCharacter
{
public:
explicit EvilBadGuy(HealthCalcFunc hcf=defaultHealthCalc)
: GameCharacter(hcf)
{...}
};

//不同的健康指数计算函数
int loseHealthQuickly(const GameCharacter&);
int loseHealthSlowly(const GameCharacter&);

//相同类型的人物搭配不同的健康计算方式
EvilBadGuy ebg1(loseHealthQuickly);
EvilBadGuy ebg2(loseHealthSlowly);


以上将健康指数计算函数移出函数题外,则函数则只能访问类的公共部分,不能访问私有成分,如果这个函数可以根据人物的public接口得来的信息计算出来就没有问题,但如果需要私有信息则就有问题了,解决方法是弱化class的封装。例如将健康计算函数设为类的友元函数,或者为私有数据提供公共访问函数。函数指针替换virtual函数,其优点(每个对象可各自拥有自己的健康计算函数和运行时期改变计算函数)是否足以弥补其缺点(降低character封装性),这在不同的设计中有不同的决择。

3 tr1::function完成stategy模式。tr1::function对象可以持有(保存)任何可调用物(如函数指针,函数对象或成员函数指针),只要其签名式兼容与需求端。则上面的设计改为调用tr1::function对象。如下:

//方案3:tr1::function函数实现strategy模式
class GameCharactor;  //前置声明
//计算健康指数的缺省算法
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter
{
public:
//代表函数是接受一个reference指向const GameCharacter,并返回int,而且这个
//函数对象可以持有任何与此前面式兼容的可调用物,即参数可被隐式转换为
//const GameCharacter&,返回值可被隐式转换为int,这个也是与方案2类的唯一
//区别,不是持有一个指针,而是持有一个tr1::function对象,相当于指向函
//数的泛化指针
typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc)
: HealthFunc(hcf)
{}

int healthValue() const
{
return healthFunc(*this);
}
private:
HealthCalcFunc heathFunc;
};
//健康计算函数:返回是是non-int,但是可以隐式转换为int,可调用
short calcHealth(const GameCharacter&);
//函数对象
struct HealthCalculator
{
int operator() (const GameCharacter&) const
{...}
};

class GameLevel
{
public:
//成员函数,用于计算健康值,non-int返回类型
float health(const GameCharacter&) const;
};

//不同的人物类型,构造函数相同
class EvilBadGuy : public GameCharacter
{};
class EyeCanyCharacter : public GameCharacter
{};

EvilBadGuy ebg1(calHealth);  //人物1,使用函数计算
EyeCandyCharacter ecc1(HealthCalculator()); //人物2,使用函数对象计算
//使用类的成员函数计算
GameLevel currentLevel;
EvilBadGuy ebg2(str::tr1::bind(&GameLevel::health, currentLevel, _1));
最后一个构造函数的解释,str::tr1::bind(&GameLevel::health, currentLevel, _1),因为要调用GameLevel的成员函数health计算健康指数,但是这个函数在类中,实际上是有两个参数的,一个是类的this指针,一个是参数GameCharacter&,为了让他转换成单一参数(一个GameCharactor),于是使用bind,将currentLevel这个对象绑定到这个计算函数所需的那个GameLevel对象上,则每次调用时计算ebg2的健康时使用。即bind的意义就是,ebg2的健康计算函数应该总是以currentLevel作为GameLevel对象。

以上演示了使用tr1::function对象可以调用不同的可调用物:函数指针,函数对象,类成员函数,好伟大阿阿!!!


4 古典的strategy模式点击打开链接(还是偶的~~),对于strategy模式,传统做法是将健康函数昨晨一个分离的继承体系中的virtual成员函数,以上两种方法都是strategy的某种形式,这个是传统形式。该例的UML图:



每个GameCharacter对象都内涵一个指针,指向一个来自HealthCalcFunc继承体系的对象。对应的代码如下:

//方案4:古典的Strategy模式
class GameCharacter;
class HealthCalcGunc
{
public:
...
virtual int calc(const GameCharacter& gc) const
{}
};

class SlowHealthLoser : public HealthCalcFunc
{};
class FastHealthLoser : public HealthCalcFunc
{};

HealthCalcFunc defaultHealthCalc;
class GameCharacter
{
public:
explicit GameCharacter(HealthCalcFunc* phcf = &defaultHealthCalc)
: pHealthCalc(phcf)
{}
int healthValue() const
{
return pHealthCalc->calc(*this);
}
private:
HealthCalcFunc* pHealthCalc;
};
//不同的人物类型,构造函数相同
class EvilBadGuy : public GameCharacter
{};
class EyeCanyCharacter : public GameCharacter
{};


总结:以上并不是virtual函数的所有替换方案,这个意思就是呀,不要总是到面向对象设计常规而形成的凹洞中,外面还有好多方法可以用呢,条条大路通罗马呀~~~

请记住:

virtual函数的替代方案包括NVI手法,Strategy设计模式的多种形式.NVI手法自身是一个特殊的Template Method设计模式。

将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问类的non-public成员。

tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物。

完毕!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: