游戏中的人物伤害值计算问题。

(一)方法(1):一般来讲能够使用虚函数的方法:

class GameCharacter {
public:
virtual int healthValue() const; //返回人物的体力值,派生类能够做出改动
...
};

这确实是一个显而易见的设计选择。但由于这种设计过于显而易见,可能不会对其他可选方法给予足够的关注。我们来考虑一些处理这个问题的其他方法。

(二)方法(2):使用NVI方法,在基类中使用一个公有的普通函数调用私有的虚函数。

class GameCharacter{
public:
int healthValue() const { //派生类不能又一次定义它
... //做一些事前工作
int retVal = doHealthValue(); //调用私有函数进行计算
... //做一些事后工作
return retVal;
}
private:
virtual int doHealthValue() const{ //派生类能够又一次定义
... //提供缺省算法
}
};

NVI手法的一个优势通过
"做事前工作" 和 "做事后工作" 两个凝视在代码中标示出来。这意味着那个外覆器能够确保在virtual函数被调用前,特定的背景环境被设置,而在调用结束后,这些背景环境被清理。比如,事前工作能够包含锁闭一个mutex,生成一条日志,校验类变量和函数的先决条件是否被满足,等等。事后工作能够包含解锁一个mutex,校验函数的事后条件,再次验证类约束条件,等等。假设你让客户直接调用virtual函),确实没有好的方法能够做到这些。

      NVI手法事实上不是必需让virtual函数一定是private。有时必须是protected(在继承体系中,子类要直接调用基类成员函数)。还有时候甚至是public,这么一来的话就不能实施NVI手法了。

(三)方法(3)使用函数指针。

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)通过定义不同的体力值计算方法,同种类型的人物通过调用不同的函数能够实现不同的计算方法:

class EvilBadGuy: public GameCharacter {
public:
explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc)
: GameCharacter(hcf)
{...}
...
};
int loseHealthQuickly(const GameCharacter&);
int loseHealthSlowly(const GameCharacter&); EvilBadGuy ebg1(loseHealthSlowly);//同样类型的人物搭配
EvilBadGuy ebg2(loseHealthQuickly);//不同的健康计算方式

(2)人物的体力计算方法能够在执行期间变更(相当于为GameCharacter的私有变量又一次赋值)。

比如GameCharacter可提供一个成员函数setHealthCalculator,用来替换当前的健康指数计算函数。

使用函数指针这样的方法(包含以后的两种方法)可能会使用类外的函数,从而减少封装性。所以在用这样的方法的时候,他的上面两种长处是否能弥补他的缺点(减少类的封装性)是我们在整个设计之前须要考虑的东西。

(四)方法(4)使用tr1::function完毕Strategy模式。

class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
//HealthCalcFunc能够是不论什么“可调用物”,可被调用并接受不论什么兼容于GameCharacter之物,返回不论什么兼容于int的东西,详下:
typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
//这样的定义表示HealthCalcFunc作为一种类型,接受GameCharacter类型的引用,并返回整数值,当中支持隐式类型转换
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf)
{}
int healthValue() const{
return healthFunc(*this);
}
...
private:
HealthCalcFunc healthFunc;
};

那个签名代表的函数是“接受一个reference指向const GameCharacter,并返回int”

std::tr1::function<int (const GameCharacter&)>

所谓兼容,意思是这个可调用物的參数可被隐式转换为const GameCharacter&,而其返回类型可被隐式转换成int。

在这里,GameCharacter持有一个tr1::function对象,相当于一个指向函数的泛化指针。

在使用这种方法时P175介绍了三种调用方式,即使用三种方式初始化GameCharacter的派生类:一个详细的函数,一个函数对象,以及一个像std::tr1::bind(&GameLevel::health,
currentLevel, _1)这样用一个对象的成员函数。

EvilBadGuy ebg1(calcHealth);        //使用某个函数
EyeCandyCharacter ecc1(HeathCalculator()); //使用某个函数对象(包括一个函数的结构体)
GameLevel currentLevel;
EvilBadGuy ebg2(std::tr1::bind(&GameLevel::health, currentLevel, _1));

完整代码像这样:

客户在“指定健康计算函数”这件事上有更惊人的弹性:

short calcHealth(const GameCharacter&); //函数return non-int
struct HealthCalculator {//为计算健康而设计的函数对象
int operator() (const GameCharacter&) const
{
...
}
};
class GameLevel {
public:
float health(const GameCharacter&) const;//成员函数,用于计算健康
...
};
class EvilBadGuy : public GameCharacter {
...
};
class EyeCandyCharacter : public GameCharacter {
...
}; EvilBadGuy ebg1(calcHealth);//函数
EyeCandyCharacter ecc1(HealthCalculator());//函数对象
GameLevel currentLevel;
...
EvilBadGuy ebg2(std::tr1::bind(&GameLevel::health, currentLevel, _1));//成员函数

GameLevel::health宣称它接受两个參数,但实际上接受两个參数,由于它也获得一个隐式參数GameLevel,也就是this所指的那个。然而GameCharacter的健康计算函数仅仅接受单一參数:GameCharacter。假设我们使用GameLevel::health作为ebg2的健康计算函数,我们必须以某种方式转换它,使它不再接受两个參数(一个GameCharacter和一个GameLevel),转而接受单一參数(GameCharacter)。于是我们将currentLevel绑定为GameLevel对象,让它在“每次GameLevel::health被调用以计算ebg2的健康”时被使用。那正是tr1::bind的作为。

(五)方法(5)使用古典的Strategy模式

将健康计算函数做成一个分离的继承体系中的virtual成员函数。

class GameCharacter;
class HealthCalcFunc {
...
virtual int calc(const GameCharacter& gc) const
{...}
...
};
HealthCalcFunc defaultHealthCalc;
class GameCharacter {
public:
explicit GameCharacter(HealthCalcFunc* phcf = &defaultHealthCalc)
:pHealthCalc(phcf);
{}
int healthValue() const
{
return pHealthCalc->calc(*this);
}
...
private:
HealthCalcFunc* pHealthCalc;
};

每个GameCharacter对象都内含一个指针,指向一个来自HealthCalcFunc继承体系的对象。

还能够提供“将一个既有的健康计算算法纳入使用”的可能性--仅仅要为HealthCalcFunc继承体系加入一个derived class就可以。

UML图在书上P176。

(六)总结:

虚函数的替代方案有:

(1)使用non-virtual interface(NVI)方法,它是Template Method设计模式的一种特殊形式。使客户通过仅有的非虚函数间接调用私有的虚函数,该公有的非虚函数称为私有虚函数的“外覆器”(wrapper)。公有的非虚函数能够在调用虚函数前后做一些其它工作(如相互排斥器的锁定与解锁,验证约束条件等)。

(2)将虚函数替换为“函数指针成员变量”,它是Strategy设计模式的一种分解表现形式。

(3)以tr1::function成员变量替换虚函数,从而同意使用不论什么可调用物搭配一个兼容于需求的签名式(这句话表达太晦涩了,非常难理解,样例见下)。它也是Strategy设计模式的某种形式。

(4)将继承体系内的虚函数替换为还有一个继承体系内的虚函数,这是Strategy设计模式的传统实现手法。

请记住:

(1)virtual函数的替代方案包含NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。

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

(3)tr1::function对象的行为就像一般函数指针。这种对象可接纳“与给定之目标签名式(target signature)兼容”的全部可调用物(callable entities)。

Effective C++:条款35:考虑virtual函数以外的其它选择的更多相关文章

  1. Effective C++ -----条款35:考虑virtual函数以外的其他选择

    virtual函数的替代方案包括NVI手法及Strategy设计模式的多种手法.NVI手法自身是一个特殊形式的Template Method设计模式. 将机能从成员函数移到class外部函数,带来的一 ...

  2. 条款35:考虑virtual函数以外的其他选择

    有一部分人总是主张virtual函数几乎总应该是private:例如下面这个例子,例子时候游戏,游戏里面的任务都拥有健康值这一属性: class GameCharacter{ public: int ...

  3. 条款35:考虑virtual函数以外的其他选择(Consider alternative to virtual functions)

    NOTE: 1.virtual 函数的替代方案包括NVI手法及Strategy设计模式的多种形式.NVI手法自身是一个特殊形式的Template Method设计模式. 2.将机能从成员函数移到外部函 ...

  4. Effective C++(9) 构造函数调用virtual函数会发生什么

    问题聚焦: 不要在构造函数和析构函数中调用virtual函数,因为这样的调用不会带来你预想的结果. 让我先来看一下在构造函数里调用一个virtual函数会发生什么结果 Demo class Trans ...

  5. effective c++ 条款7 declare virtual destructor for polymophyc base class

    这似乎很明显. 如果base class的destructor不是virtual,当其derived class作为基类使用,析构的时候derived class的数据成员将不会被销毁. 举个例子 我 ...

  6. 读书笔记_Effective_C++_条款三十五:考虑virtual函数以外的其他选择

    举书上的例子,考虑一个virtual函数的应用实例: class GameCharacter { private: int BaseHealth; public: virtual int GetHea ...

  7. 考虑virtual函数以外的其它选择

    详情见<Effective C++>item35 1.使用non-virtual interface(NVI)手法,这是Template Method设计模式的一种特殊形式. 它以publ ...

  8. 读书笔记 effective c++ Item 35 考虑虚函数的替代者

    1. 突破思维——不要将思维限定在面向对象方法上 你正在制作一个视频游戏,你正在为游戏中的人物设计一个类继承体系.你的游戏处在农耕时代,人类很容易受伤或者说健康度降低.因此你决定为其提供一个成员函数, ...

  9. [EffectiveC++]item35:考虑virtual函数以外的其他选择

    本质上是说了:   Template Pattern & Strategy Pattern 详细见<C++设计模式 23种设计模式.pdf 55页> 宁可要组合 不要继承. ——— ...

随机推荐

  1. eclipse在ubuntu13.04下崩溃crash

    错误信息: # # A fatal error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0 ...

  2. Makefile 工程管理

    Makefile 工程管理 Makefile 规则 --变量 在Makefile中,用户除了可以自己定义变量外,还可以使用存在系统已经定义好的默认变量 $^:代表所有的依赖文件 $@:代表目标 $&l ...

  3. Android向Rest服务Post数据遇到的Date类型数据问题

    今天在Android端向Rest服务Post数据时,总是不成功,查了很多资料,才知道Rest端将json串反序列化时,需要的时间格式必须是UTC类型,及Date(12345678+0800)格式. A ...

  4. [canvas]通过动态生成像素点做绚丽效果

    本例中的粒子就是实实在在的像素,由js代码在canvas上动态生成的像素点!这些像素点通过一个运动方法有规律地动了起来.透过这个思路,我们可以想到很多很炫的效果,但是这个性能有待考察.实验证明,动态控 ...

  5. Xcode6和Xcode5获取app名字

    1.在Xcode5下,获取程序名字(app name)的方法为: NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionar ...

  6. 某Java游戏服务器用到的知识

    Runtime.getRuntime().addShutdownHook(shutdownHook); shutdownHook()函数可以在jvm关闭的时候进行内存清理.对象销毁等操作 http:/ ...

  7. uva 12100 Printer Queue

    The only printer in the computer science students' union is experiencing an extremely heavy workload ...

  8. 为什么在CSS中不要再使用@import

    http://www.stevesouders.com/blog/2009/04/09/dont-use-import/为什么在CSS中不要再使用@import

  9. tomcat之负载均衡(apache反响代理tomcat)

    基于mod_proxy模块 配置内容如下: 准备工作-->检查模块 # httpd -D DUMP_MODULES……………………proxy_module (shared)proxy_balan ...

  10. 【3】python核心编程 第六章-序列:字符串、列表和元组

    1.序列类型操作符 序列操作符 作用 seq[ind] 获得下标为ind 的元素 seq[ind1:ind2] 获得下标从ind1 到ind2 间的元素集合 seq * expr 序列重复expr 次 ...