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

(一)方法(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. iOS多线程及其感悟

    感觉每天都是匆匆忙忙的,每天似乎都是时间不够用一样,可是等真的想要动手敲代码的时候才发现,原来还有好多好多的知识点不是太熟练,所以,人不可以一直感觉自我良好, 有时间就是那种自我感觉良好的心态毁了自己 ...

  2. LinkedList的分析(转)

    一.源码解析 1. LinkedList类定义. public class LinkedList<E> extends AbstractSequentialList<E> im ...

  3. Margin是什么?

    Margin是什么 CSS 边距属性定义元素周围的空间.通过使用单独的属性,可以对上.右.下.左的外边距进行设置.也可以使用简写的外边距属性同时改变所有的外边距.——W3School 边界,元素周围生 ...

  4. java String常见的处理

    import java.util.Arrays; class Demo5 { public static void main(String [] args) { String name1=" ...

  5. Canvas Api简介1

    canvas canvas 其实对于HTML来说很简单,只是一个标签元素而已,自己并没有行为,但却把一个绘图 API 展现给客户端 JavaScript 以使脚本能够把想绘制的东西都绘制到一块画布上, ...

  6. Toolbar 和 CollapsingToolbarLayout一起使用时menu item无点击反应解决办法

    昨天一直在琢磨为什么Toolbar和CollapsingToolbarLayout一起使用时menu item无点击放应的原因,后来在stackoverflow上一条回答,说可能是Toolbar的背景 ...

  7. 跨域调用webservice

    本人第一次在博客园写博客. 最近研究js的跨域调用,举个小例子. ASP.net 中webservice 源代码 /// <summary>    /// Service1 的摘要说明   ...

  8. 2014年1月9日 Oracle常见授权与权限回收[转]

    1.GRANT 赋于权限 常用的系统权限集合有以下三个: CONNECT(基本的连接), RESOURCE(程序开发), DBA(数据库管理) 常用的数据对象权限有以下五个: ALL ON 数据对象名 ...

  9. ORA-14450

    ORA-14450 attempt to access a transactional temp table already in use Cause: An attempt was made to ...

  10. iOS 证书错误 Certificates下面的 App Store and Ad Hoc是灰的?? 点不了

    原因 因为一个用户名下只能同时有一个发布证书,你之前建立了某个证书并且没有使用的话就无法再创建了,先把它撤销或者使用后才可以继续创建新的