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

(一)方法(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. java与.net比较学习系列(4) 运算符和表达式

    上一篇总结了java的数据类型,得到了冰麟轻武等兄弟的支持,他们提出并补充了非常好的建议,在这里向他们表示感谢.在后面的文章中,我会尽力写得更准确和更完善的,加油! 另外,因为C#是在java之后,也 ...

  2. .net 4.5 新特性 async await 一般处理程序实例

    using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Sys ...

  3. Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6664554 在上一文章Android系统匿名共 ...

  4. Codeforces 474C Captain Marmot 给定4个点和各自旋转中心 问旋转成正方形的次数

    题目链接:点击打开链接 题意: 给定T表示case数 以下4行是一个case 每行2个点,u v 每次u能够绕着v逆时针转90° 问最少操作多少次使得4个u构成一个正方形. 思路: 枚举判可行 #in ...

  5. Git 多人协作的工作模式

    多人协作 148次阅读 当你从远程仓库克隆时,实际上Git自动把本地的master分支和远程的master分支对应起来了,并且,远程仓库的默认名称是origin. 要查看远程库的信息,用git rem ...

  6. Git 笔记二-Git安装与初始配置

    git 笔记二-Git安装与初始配置 Git的安装 由于我日常生活和工作基本上都是在Windows上,因此此处只说windows上的安装.Windows上的安装和其他程序一样,只需要到http://g ...

  7. 利用IIS7 解决URL访问限制问题

    网站可以通过URl直接访问一些不希望被访问的东西, 比如一些图片,js,css等等. 为了解决这个问题看了好多文章,不过毕竟我是新手菜鸟级别的,没有具体的解决方法,真心不知道怎么弄. 今天在看IIS的 ...

  8. C#创建文件夹、文件

    private void CheckCatcheDirectory()//创建文件夹      {          if (!Directory.Exists(xmlFilePath))//xmlF ...

  9. Android中获取apk基本信息

    一 PackageManager可以获得的所有包节点信息: 1,所有节点的基类:PackageItemInfo: 2,PackageInfo:package的全面信息,与AndroidManifest ...

  10. SQL Server -SET NOCOUNT

    SET NOCOUNT 使返回的结果中不包含有关受 Transact-SQL 语句影响的行数的信息. 语法 SET NOCOUNT { ON | OFF } 注释 当 SET NOCOUNT 为 ON ...