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

(一)方法(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. windows下用vs2008和boost结合编译程序

      原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://co63oc.blog.51cto.com/904636/504469 win ...

  2. My way to Python - Day02

    版权声明: 本文中的资料均来自于互联网.将各路内容摘抄于此,作为学习笔记,方便用作后面翻阅查看.如果原作者对文中内容的引用有任何版权方面的问题,请随时联系,我将尽快处理. 特别鸣谢:武沛齐 <P ...

  3. HDU 5618 Jam's problem again

    题意: 三维坐标,对于1个点,找出有多少个点,3个坐标都比该点小! Sample Input 1 4 10 4 7 10 6 6 8 2 5 7 3 10   Sample Output 1 1 0 ...

  4. asp.net 页面实践执行顺序

    protected void Page_Load(object sender, EventArgs e) { } #region OnPreInit 第一步 protected override vo ...

  5. WCF入门教程系列二

    一.概述 WCF能够建立一个跨平台的安全.可信赖.事务性的解决方案,是一个WebService,.Net Remoting,Enterprise Service,WSE,MSMQ的并集,有一副很经典的 ...

  6. [C++程序设计]指针数组和指向指针的指针

    指针数组的概念 一维指针数组的定义形式为 类型名*数组名[数组长度]; 例如 int *p[4]; 可以用指针数组中各个元素分别指向若干个字符串,使字符串处理更加方便灵活

  7. [C++程序设计]用指向数组的指针作函数参数

    #include <iostream> using namespace std; int main() { ]); ][]={,,,,,,,,,,,}; output(a); ; } ]) ...

  8. 用Dockerfile构建docker image

    dockerfile是为快速构建docker image而设计的,当你使用docker build 命令的时候,docker 会读取当前目录下的命名为Dockerfile(首字母大写)的纯文本文件并执 ...

  9. 利用cookies获取登录后的网页

    众所周知,HTTP连接是无状态的,那么问题来了,怎么记录用户的登录信息呢?通常的做法是用户第一次发送HTTP请求时,在HTTP Server端生成一个SessionID,SessionID会对应每个会 ...

  10. Java--static interface

    http://stackoverflow.com/questions/8374646/what-is-a-static-interface-in-java http://stackoverflow.c ...