假设你正在写一个游戏,你打算为游戏内的人物设计一个继承体系。
人物被伤害会掉血,因此你提供了一个成员函数healthValue
,返回一个整数,表示人物的健康程度.由于不同的人物可能用不同的方式计算它们的掉血情况,将healthValue
声明为virtual
是显而易见的做法:
class GameCharacter
{
public:
virtual int healthValue()const;
};
由于这个设计如此明显,从某个角度来说却成为了它的缺点,因为你可能没有认真考虑过其他替代方法。让我们来考虑其他的解法。
借由Non-Virtual Interface手法实现Template Method模式
这个设计的思路是:virtual
函数应该总是private
的,较好的设计是保留healthValue
为public
成员函数,但让它成为非虚函数,并调用一个私有虚函数进行实际工作.
比如:
class GameCharacter
{
public:
int healthValue()const
{
...
int retVal = doHealthValue();
...
return retVal;
}
...
private:
virtual int doHealthValue()const
{
...
}
};
让客户通过public no-virtual
成员函数简洁调用private virtual
函数.
称为non-virtual interface
(NVI
)手法。是Template Method设计模式的一个独特的表现形式。
非虚函数称为虚函数的外覆器.
这个手法的优点是:你可以在外覆器中做一些事前工作和事后工作,事前工作比如锁定互斥器,验证函数先决条件、验证class约束条件等.事后工作包括互斥器解除锁定,验证函数的事后条件等等.
借由函数指针实现策略模式
还有一种设计主张是:人物健康指数的计算与人物类型无关。
这样的计算不需要人物这个成分,例如我们可能会要求每个人物的构造函数接受一个指针,指向一个健康计算函数,而我们可以调用该函数进行实际计算:
class GameCharacter;
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 healthFunc;
};
这个做法的优点如下:
1.同一人物类型之不同实体可以有不同的健康计算函数
2.某个人物健康指数计算函数可以在运行期变更。
总结:
1.virtual函数的替代方案包括NVI手法以及策略设计模式的多种形式
2.将机能成从成员函数移动类外部函数,带来的一个缺点是:非成员函数无法访问类的非共有成员.