小企业网站建设查询,html网页编程软件,营销型外贸网站制作,成都科技网站建设电话咨询1.前言
假设我们在写一个3D游戏软件#xff0c;打算为游戏内的人物设计一个继承体系。游戏内容属于暴力砍杀类型#xff0c;游戏中的角色被伤害或其它因素导致健康状态下降的情况是一个常见属性。因此设计一个成员函数healthValue#xff0c;它会返回一个整数#xff0c;表…1.前言
假设我们在写一个3D游戏软件打算为游戏内的人物设计一个继承体系。游戏内容属于暴力砍杀类型游戏中的角色被伤害或其它因素导致健康状态下降的情况是一个常见属性。因此设计一个成员函数healthValue它会返回一个整数表示人物的健康程度。
2.具体分析
由于不同的任务可能以不同的方式计算它们的健康指数将healthValue声明为virtual似乎是最直白的做法
class GameCharacter{public:virtual int healthValue() const;//返回人物的健康指数....
};在这里healthyValue并未被声明未pure virtual这暗示我们将会有个计算健康指数的缺省算法。这的确是个很直白的设计但是从某个角度说却反而成为它的弱点了。由于这个设计太过于明显导致我们可能都没有认真考虑过其它的替代方案这里我们考虑用其它方案来替代。
由Non-Virtual Interface手法实现Template Method模式
首先我们先从一个思想流派说起该流派认为virtual函数应该几乎总是private。该流派认为较好的设计是保留healthValue为public成员函数但让它成为non-virtual并调用一个private virtual函数比如doHealthValue进行实际工作
class GameCharacter{public:int healthValue() const{...int retValdoHealthValue();...return retVal;}...private:virtual int doHealthValue() const//derived classes可重新定义该函数{...//缺省算法计算健康指数}
};
在这段代码中我直接在class定义式内呈现函数本体。
这一基本设计也就是”令客户通过public non-virtual成员函数间接调用private virtual函数“称该方法为NVInon-virtual interface手法。它是所谓template Method设计模式的一个独特表现形式我把这个non-virtual函数healthValue称为virtual函数的外覆器
该方法的一个优点是在上述代码注释”做一些事前工作“和”做一些事后工作“之中。那些注释用来告诉你当时的代码保证在”virtual函数进行真正工作之前和之后被调用“。这意味着外覆器wrapper确保得以在一个virtual函数被调用之前设定好适当的场景并在调用结束之后清理场景。”事前工作“可以包括锁定互斥器locking a mutex,制造运转日志记录项log entry,验证class约束条件验证函数先决条件。”事后工作“可以包括互斥器解锁锁定unlocking a mutex验证函数的事后条件再次验证class约束条件等等倘如让客户直接调用virtual函数就没有啥办法做这些事情了。
NVI方案涉及在derived classes内重新定义private virtual函数。”重新定义virtual函数“表示某些事如何被完成”调用virtual函数“表示其什么时候被完成。这些事情都是各自独立互不相关的。NVI方案允许derived classes重新定义virtual函数但base class保留”函数何时被调用“的权利。
在NVI方案下其实没有必要让vitual函数一定得是private某些class继承体系要求derived class在virtual函数的实现内必须调用base class的对应函数而为了让这样的调用合法virtual函数必须是protected不能是private。有时候virtual函数甚至一定是public这种情况下就不能实施NVI方案了。
籍由Function Pointers实现Strategy模式
NVI方案对public virtual函数而言是一个有趣的替代方案但从某种设计角度来说它还没脱离virtual的本质。毕竟我们还是使用virtual函数来计算每个人物的健康指数。
这里有另外一种设计方案该方案主张”人物健康指数的计算与人物类型无关“这样的计算完全不需要“人物”这个成分。例如我们可能会要求每个人物的构造函数接收一个指针指向一个健康计算函数而我们可以调用该函数进行实际计算
class GameCharacter;//前置声明
//以下是函数计算健康指数的缺省算法
int defaultHealthCalc(const GameCharacter gc);
class GameCharacter{public:typedef int(*HealthCalcFunc)(const GameCharacter);explict GameCharacter(HealthCalcFunc hcfdefaultHealthCalc):healthFunc(hcf){ }int healthValue() const{return healthFunc(*this);}....private:HealthCalcFunc healthFunc;};
这种做法是常见的Strategy设计模式的简单应用它与GameCharacter继承体系内的virtual函数相比具有以下这些特点
同一人物类型的不同实体可以有不同的健康计算函数比如
class EvilBadGuy:public GameCharacter{public:explict EvilBadGuy(HealthCalcFunc hcfdefaultHealthCalc):GameCharacter(hcf){...}
};
int loseHealthQuickly(const GameCharacter);//健康指数计算函数1
int loseHealthSlowly(const GameCharacter);//健康指数计算函数2
EvilBadGuy ebg1(loseHealthQuickly);//相同类型的人物搭配
EvilBadGuy ebg2(loseHealthSlowly);//不同的健康计算方式
若已知人物的健康计算函数可以在运行期间变更例如GameCharacter可提供一个成员函数setHealthCalculator用来替换当前的健康指数计算函数。
换句话说健康指数计算函数不再是GameCharacter继承体系内的成员函数这一事实意味着计算函数并未特别访问“即将被计算健康指数”的那个对象的内部成分。例如defaultHealthCalc并未访问EvilBadGuy的non-public成分。
如果人物的健康可单纯根据该人物public接口得来的信息加以计算这就没有问题但如果需要non-public信息进行精确计算就有问题了。实际上任何时候当你将class内的某个机能也许来自某个成员函数替换为class外部的某个等价机能也许取到自某个non-member non-friend函数或另一个class的non-friend成员函数这都是潜在争议点。
一般而言唯一能够解决“需要以non-member函数访问class的non-public成分”的办法就是弱化class的封装比如class可声明为non-member函数为friends或是为其实现的某一部分提供public访问函数。运用函数指针替换virtual函数。
籍由trl::function完成Strategy模式
一旦习惯了templates以及它们对隐式接口的使用基于函数指针的做法看起来就显得过于死板了。这里有个问题为什么一定得是函数为什么不能够是个成员函数为什么一定得返回Int而不是任何可被转换为int得类型呢
假设我们不再使用函数指针如前面得healthFunc),而是改用一个类型为trl::function的对象这些约束就全部消失不见了。见以下例子
trl::function:
class GameCharacter;
int defaultHealthCalc(const GameCharacter gc);
class GameCharacter{//HealthCalcFunc可以是任何“可调用物”可被调用并接受//任何兼容于GameCharacter之物返回任何兼容于int的东西typedef std::trl::functionint (const GameCharacter) HealthCalcFunc;explict GameCharacter(HealthCalcFunc hcfdefaultHealthCalc):healthFunc(hcf){ }int healthValue() const{return healthFunc(*this);}...private:HealthCalcFunc healthFunc;};
如你所见HealthCalcFunc是个typedef,用来表现trl::function的某个具现化意味着该具现化的行为像一个一般的函数指针。现在我们靠近一点瞧瞧HealthCalcFunc是个什么样的typedef:
HealthCalcFunc是个什么样的typedef:
std::trl::functionint (const GameCharacter)
这里我把trl::function具现体的目标签名以不同的颜色强调出来。那个签名代表的函数是“接受一个reference指向const GameCharacter并返回int”。这个trl::function类型即HealthCalcFunc产生的对象可以持有任何与此签名式兼容的可调用物。所谓兼容即这个可调用物的参数可以被隐式转换为const GameCharacter,而其返回类型可被隐式转换为int。
和前一个设计比较这个设计几乎相同。唯一不同的是如今GameCharacter持有一个trl::function对象相当于一个指向函数的泛化指针。见下例子
short calcHealth(const GameCharacter);//健康计算函数
struct HealthCalculator{//为计算健康而设计的函数对象int operator() (const GameCharacter) const{...}};
class GameLevel{public:float health(const GameCharacter) const;//成员函数用以计算健康...//注意其non-int返回类型
};
class EvilBadGuy:public GameCharacter{...
};class EyeCandyCharacter:public GameCharacter{//另外一个人物类型假设其构造函数与EvilBadGuy同....
};EvilBadGuy ebg1(calcHealth);//人物1使用某个函数计算健康指数
EyeCandyCharacter eccl(HealthCalculator())//人物2使用某个函数对象计算健康指数
GameLevel currentLevel;
...
EvilBadGuy ebg2(std::trl::bind(GameLevel::health,currentLevel,_l));//人物3使用某个成员函数计算健康指数
就我个人而言当我发现trl::function允许我们做的事时非常惊讶。
首先我要表明为计算ebg2的健康指数应该使用GameLevel class的成员函数health,GameLevel::health宣称它自己接受一个参数那是reference 指向GameCharacter但实际上它接受两个参数因为它也获得一个隐式参数GameLevel也就是this所指的那个。然而GameCharacter的健康计算函数只接受单一参数GameCharacter如果我们使用GameLevel::health作为ebg2的健康计算函数我们必须以某种方式转换它使它不再接受两个参数转而接受单一参数一个GameCharacter。
3.总结
本条款的根本忠告是当你为解决问题寻找某个设计方法时不妨考虑virtual函数的替代方案。
1.使用non-virtual interface方案那是Template method设计模式的一种特俗形式。它以public non-virtual成员函数包裹较低访问性的virtual函数
2.将virtual函数替换为“函数指针成员变量”这是strategy设计模式的一种分解表现形式
3.以trl::function成员变量替换virtual函数因而允许使用任何可调用物搭配一个兼容于需求的签名式
4.将继承体系内的virtual函数替换为另一个继承体系内的virtual函数这是strategy设计模式的传统实现方法。