条款35:考虑virtual函数以外的其他选择
假设你在开发一款游戏,你打算为游戏内的人物设计一个继承体系。每个人物都有自己的健康状态。你能马上想到下面这个设计:
class GameCharacter{ public: virtual int healthValue()const; };
有没有其他替代方案呢?答案是肯定的。
1、藉由Non-Virtual Interface手法实现Template Method模式
//GameCharacter.h #ifndef GAMECHARACTER_H #define GAMECHARACTER_H class GameCharacter{ public: int healthValue() const; private: virtual int getInitialValue() const; virtual int doHealthValue(int healthValue) const; }; int GameCharacter::healthValue() const{ int retVal = getInitialValue(); retVal = doHealthValue(retVal); return retVal; } int GameCharacter::getInitialValue() const{ return 100; } int GameCharacter::doHealthValue(int healthValue) const{ return healthValue; } #endif //EvilBadGuy.h #ifndef EVILBADGUY_H #define EVILBADGUY_H #include"GameCharacter.h" class EvilBadGuy:public GameCharacter{ private: virtual int getInitialValue() const; virtual int doHealthValue(int healthValue) const; }; int EvilBadGuy::getInitialValue() const{ return 400; } int EvilBadGuy::doHealthValue(int healthValue) const{ return healthValue / 2; } #endif //main.cpp #include"EvilBadGuy.h" #include<iostream> using namespace std; int main(){ EvilBadGuy badGuy; cout << badGuy.healthValue() << endl; system("pause"); return 0; }
在基类GameCharacter中healthValue成员函数调用了doHealthValue函数,这种”令客户通过public non-virtual成员函数间接调用private virtual函数“,称为non-virtualinterface(NVI)手法。这个non-virtual(healthValue)函数称为virtual(doHealthValue)函数的外覆器(wrapper)。
NVI手法的一个优点是外覆器确保得以在一个virtual函数被调用之前设定好适当场景,并在调用结束之后清理场景。NVI手法允许继承类重新定义virtual函数,从而赋予它们”如何实现机能“的控制能力,但基类保留”函数何时被调用“的权利。
2、藉由Function Pointers实现Strategy模式
//GameCharacter.h #ifndef GAMECHARACTER_H #define GAMECHARACTER_H class GameCharacter; int defaultHealthCalc(const GameCharacter& gc); class GameCharacter{ public: typedef int(*HealthCalcFunc)(const GameCharacter&); explicit GameCharacter(HealthCalcFunc hfc = defaultHealthCalc) :healthFunc(hfc){} int healthValue() const { return healthFunc(*this); } virtual int getInitialValue() const { return 50; } private: HealthCalcFunc healthFunc; }; int defaultHealthCalc(const GameCharacter& gc){ return gc.getInitialValue(); } #endif //EvilBadGuy.h #ifndef EVILBADGUY_H #define EVILBADGUY_H #include"GameCharacter.h" class EvilBadGuy :public GameCharacter{ public: explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) :GameCharacter(hcf){} virtual int getInitialValue() const{ return 400; } }; int loseHealthQuickly(const GameCharacter& gc){ int retVal = gc.getInitialValue(); retVal /= 4; return retVal; } int loseHealthSlowly(const GameCharacter& gc){ int retVal = gc.getInitialValue(); retVal /= 2; return retVal; } #endif //main.cpp #include"EvilBadGuy.h" #include<iostream> using namespace std; int main(){ EvilBadGuy badGuy1(loseHealthQuickly); cout << badGuy1.healthValue() << endl; EvilBadGuy badGuy2(loseHealthSlowly); cout << badGuy2.healthValue() << endl; system("pause"); return 0; }
这个做法是Strategy设计模式的简单应用,它提供了某些有趣弹性:
a、同一人物类型的不同实体可以有不同的健康计算函数,如main函数中的badGuy1和badGuy2。
b、某已知人物的健康指数计算函数可在运行期变更,如GameCharacter可提供一个成员函数setHealthCalculator,用来替换当前的健康指数计算函数。
3、藉由tr1::function完成Strategy模式
//GameCharacter.h #ifndef GAMECHARACTER_H #define GAMECHARACTER_H #include<functional> class GameCharacter; short defaultHealthCalc(const GameCharacter& gc);//注意其返回类型不是int class GameCharacter{ public: typedef std::function<int(const GameCharacter&)> HealthCalcFunc; explicit GameCharacter(HealthCalcFunc hfc = defaultHealthCalc) :healthFunc(hfc){} int healthValue() const { return healthFunc(*this); } virtual int getInitialValue() const { return 50; } private: HealthCalcFunc healthFunc; }; short defaultHealthCalc(const GameCharacter& gc){ return gc.getInitialValue(); } #endif //EvilBadGuy.h #ifndef EVILBADGUY_H #define EVILBADGUY_H #include"GameCharacter.h" class EvilBadGuy :public GameCharacter{ public: explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) :GameCharacter(hcf){} virtual int getInitialValue() const{ return 400; } }; struct LoseHealthQuickly{ int operator()(const GameCharacter& gc) const{ int retVal = gc.getInitialValue(); retVal /= 4; return retVal; } }; class GameLevel{ public: float loseHealthSlowly(const GameCharacter& gc) const{//注意返回类型不是int float retVal = static_cast<float>(gc.getInitialValue()); retVal /= 2; return retVal; } }; #endif //main.cpp #include"EvilBadGuy.h" #include<iostream> using namespace std; int main(){ EvilBadGuy badGuy1;//默认普通函数计算健康指数 cout << badGuy1.healthValue() << endl; LoseHealthQuickly loseHealthQuickly;//函数对象计算健康指数 EvilBadGuy badGuy2(loseHealthQuickly); cout << badGuy2.healthValue() << endl; GameLevel currentLevel;//成员函数计算健康指数 EvilBadGuy badGuy3(bind(&GameLevel::loseHealthSlowly, currentLevel, placeholders::_1)); cout << badGuy3.healthValue() << endl; system("pause"); return 0; }
这个做法与前面一个例子的区别:没有定义类型确定的函数指针,而是定义了一个类型为tr1:: function的对象。它的返回值为int或可转换为int的类型,输入参数为constGameCharacter引用或可以转化为constGameCharacter引用的类型。这样的对象可持有任何可调用物,即可以是一般函数指针、函数对象或成员函数指针,只要其签名式兼容于需求端。
对非静态成员函数,需要通过bind绑定。为了计算badGuy3的健康函数,需要使用GameLevel里面的loseHealthSlowly函数,这个函数实际上有两个参数:*this和GameCharacter&,而HealthCalcFunc只接受一个参数:GameCharacter&。需要将GameLevel类型中的loseHealthSlowly函数与调用它的对象绑定起来,此后每次调用loseHealthSlowly函数,都是调用绑定的那个对象的loseHealthSlowly函数。其中_1是占位符,表示的是这个函数的第一个参数。
4、古典的Strategy模式
//GameCharacter.h #ifndef GAMECHARACTER_H #define GAMECHARACTER_H #include"HealthCalcFunc.h" extern HealthCalcFunc defaultHealthCalc; class GameCharacter{ public: explicit GameCharacter(HealthCalcFunc* phcf = &defaultHealthCalc) :pHealthFunc(phcf){} int healthValue() const { return pHealthFunc->calc(*this); } virtual int getInitialValue() const { return 50; } private: HealthCalcFunc* pHealthFunc; }; #endif //EvilBadGuy.h #ifndef EVILBADGUY_H #define EVILBADGUY_H #include"GameCharacter.h" class EvilBadGuy :public GameCharacter{ public: explicit EvilBadGuy(HealthCalcFunc* phcf = &defaultHealthCalc) :GameCharacter(phcf){} virtual int getInitialValue() const{ return 400; } }; #endif //HealthCalcFunc.h #ifndef HEALTHCALCFUNC_H #define HEALTHCALCFUNC_H class GameCharacter; class HealthCalcFunc{ public: virtual int calc(const GameCharacter& gc) const; }; class LoseHealthQuickly :public HealthCalcFunc{ public: virtual int calc(const GameCharacter& gc) const; }; class LoseHealthSlowly :public HealthCalcFunc{ public: virtual int calc(const GameCharacter& gc) const; }; #endif //HealthCalcFunc.cpp #include"GameCharacter.h" int HealthCalcFunc::calc(const GameCharacter& gc) const{ int retVal = gc.getInitialValue(); return retVal; } int LoseHealthQuickly::calc(const GameCharacter& gc) const{ int retVal = gc.getInitialValue(); retVal /= 4; return retVal; } int LoseHealthSlowly::calc(const GameCharacter& gc) const{ int retVal = gc.getInitialValue(); retVal /= 2; return retVal; } HealthCalcFunc defaultHealthCalc; //main.cpp #include"EvilBadGuy.h" #include<iostream> using namespace std; int main(){ EvilBadGuy badGuy1; cout << badGuy1.healthValue() << endl; LoseHealthQuickly loseHealthQuickly; EvilBadGuy badGuy2(&loseHealthQuickly); cout << badGuy2.healthValue() << endl; LoseHealthSlowly loseHealthSlowly; EvilBadGuy badGuy3(&loseHealthSlowly); cout << badGuy3.healthValue() << endl; system("pause"); return 0; }
这个解法的吸引力在于熟悉标准Strategy模式的人很容易辨认它,可扩展性很强,可以加入不同的角色,只要从GameCharacter中派生即可,也可以加入新的健康算法,只要从HealthCalcFunc中派生即可。这个方法称为strategy模式,它的定义如下:Strategy模式定义了一系列的算法,将它们每一个进行封装,并使它们可以相互交换。Strategy模式使得算法不依赖于使用它的客户端。
对于虚函数,有以下几种替代方案:
a、 使用non-virtualinterface(NVI)手法。
b、将virtual函数替换为“函数指针成员变量“。这是Strategy设计模式的一种分解表现形式。
c、以tr1::function成员变量替换virtual函数。这也是Strategy设计模式的某种形式。
d、将继承体系内的virtual函数替换为另一个继承体系内的virtual函数。这是Strategy设计模式的传统实现方法。
请记住:
- virtual函数的替代方案包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。
- 将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
- tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式(target signature)兼容”的所有可调用物(callable entities)。
版权声明:本文为博主原创文章,未经博主允许不得转载。