这个条款主要通过一个继承体系来体现:
class GameCharacter {
public:
virtual int healthValue() const;
...
};
因为不同的人物会有不同的方式来计算,所以,需要将healthValue设置为 virtual 是非常正确的,但它并不是 纯虚函数,所以,我们有个计算健康值的缺省算法。
但是,这样做并不是最完美,它也有缺陷,所以,有没有其他方式来替代呢?
本条款就是介绍下列这些解决的方法。
这个方法,主张 virtual函数 应该几乎总是 private。
所以,它们的方法就是 保留healthValue为public,但让它成为 non-virtual,并调用一个 private virtual函数,来进行实际函数
class GameCharacter {
public:
int healthValue() const { // 派生类 不重新定义它
... // 做一些 其他工作
int retVal = doHealthValue();
...
return retVal;
}
...
private:
virtual int doHealthValue() const { // 派生类可以重新定义它
... // 缺省算法,可以重新定义
}
};
让用户通过 public non-virtual成员函数 间接调用 private virtual函数,这种方法称作 non-virtual interface(NVI),也叫作 Template Method设计模式。
这个方法主张,健康指数的计算与人物类型无关,因此就不需要这个成分,完全可以让每个人物通过一个函数来自己计算。
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
typdef int (*HealthCalcFunc) (const GameCharacter&);
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf)
{}
int healthValue() const
{ return healthFunc(*this); }
...
private:
HealthCalcFunc healthFunc;
};
这个方法就是 Strategy 设计模式的应用。它相较于在人物类内设置virtual函数有几个特性。
但是,它也有自己的局限性——人物的健康根据该人物的public接口得来的信息加以计算就没问题,但是要用到non-public信息计算,就会出问题。
总之,解决 需要用non-member函数访问class的non-public成分的方法就是 弱化class的封装。例如:
1. class可声明那个non-member函数为friends,或是为其实现的某一部分提供public访问函数
2. 运用函数指针替换virtual函数
只要习惯通过templates以及它们对隐式接口的使用,基于函数指针的做法看起来便过分苛刻且死板了。
我们可以不用函数指针,而是用 tr1::function,这个对象可以保存任何 可调用户。
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf)
{}
int healthValue() const
{ return healthFunc( *this); }
...
private:
HealthCalcFunc healthFunc;
};
HealthCalcFunc是个typedef,用来表现 tr1::function的某个实体,行为像一个函数指针。
它的含义就是 接受一个指向GameCharacter的引用,并返回int。
最后实现起来,就是这样的:
short calcHealth(const GameCharacter&);
struct HealthCalculator { // 为计算健康设计的成员对象
int operator() (const GameCharacter&) const
{ ... }
};
class GameLevel {
public:
float health(const GameCharacter&) const; // 计算健康的成员函数
...
};
class EvilBadGuy: public GameCharacter {
... // 同前
};
class EveCandyCharacter: public GameCharacter {
... // 另一个人物类型,假设构造函数同EvilBadGuy
};
EvilBadGuy edg1(calcHealth); // 人物1,使用某个函数计算健康指数
EyeCandyCharacter ecc1(HealthCalculator()); // 人物2,使用某个函数对象计算健康指数
GameLevel currentLevel;
...
EvilBadGuy ebg2( std::tr1::bind(&GameLevel::health, currentLevel, _1) // 人物3,使用某个成员函数计算健康指数
};
这里在 ebg2中第三个参数是_1,它意味着 当为 ebg2调用GameLevel::health时,是通过currentLevel作为GameLevel对象。
但是,这不是重点,重点是,通过tr1::function替换函数指针,就可以允许用户在计算人物健康指数时使用任何兼容的可调用物。
按方法3的那几个类来讲,通过古典的 Strategy模式来设计,GameCharacter类是某个继承体系的根类,而EvilBadGuy类与EyeCandyCharacter类都是派生类;而 HealthCalcFunc是另一个继承体系的根类,在这个体系也有一些其他的派生类;每一个GameCharacter对象都内含一个指向HealthCalcFunc继承体系对象的指针。
class GameCharacter;
class HealthCalcFunc {
public:
...
virtual int clac(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;
};
这个方法对于熟悉标准Strategy模式的人易理解使用,而且它还提供了 将一个既有的健康算法纳入使用的可能性。也就是在HealthCalcFunc继承体系中加派生类。