effective C++: 6.继承与面向对象设计(二)

2014-11-24 03:15:38 · 作者: · 浏览: 6
alue();
...//做一些事后工作
return retVal;
}
private:
virtual int doHealthValue() const //derived class 可以重新定义它。
{
...//缺省计算,计算健康指数
}
};
这一基本设计,“令客户通过public non-virtual成员函数间接调用private virtual函数”,称为non-virtual interface(NVI)手法。是所谓template Method设计模式的一个独特表现形式。把这个non-virtual函数(healthValue)称为virtual函数的外覆器(wrapper)。
NVI的优点在上述代码注释“做一些事前工作”和“做一些事后工作”之中。这意味着外覆器(wrapper)确保得以在一个virtual函数被调用之前设定好适当场景,并在调用结束之后清理场景。“事前工作”可以包括锁定互斥器、制造运转日志记录项、验证class约束条件、验证函数先决条件等等。
NVI手法涉及在derived class内重新定义private virtual函数。重新定义若干个derived class并不调用的函数!这里并不矛盾。“重新定义virtual函数”表示某些事“如何”被完成,“调用virtual函数”则表示它“何时”被完成。NVI允许derived class重新定义virtual函数,从而赋予它们“如何实现机能”的控制能力,但base class保留诉说“函数何时被调用”的权力。
但又是不能有NVI手法,比如构造函数不能调用虚函数。

2.藉由Function Pointers实现Strategy模式
另一个设计主张是“人物健康指数的计算与人物类型无关”,这样的计算完全不需要“人物”这个成分。例如我们可能会要求每个人物的构造函数接受一个指针,指向一个健康计算函数,而我们可以调用该函数进行实际计算:
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;
};
这个做法是常见的Strategy设计模式的简单应用。和“GameCharacter继承体系内的virtual函数”的做法比较,它提供了某些有趣弹性:
同一人物类型不同实体可以有不同的健康计算函数。例如:
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);//不同的健康计算方式
某已知人物健康指数计算函数可以在运行期变更:例如GameCharacter可提供一个成员函数setHealthCalculator,用来替换当前的健康指数计算函数。
这些计算函数并未特别访问“即将被计算健康指数”的那个对象内部成分。例如defaultHealthCalc并未访问EvilBadGuy的non-public成分。如果需要non-public信息进行精确计算,就有问题了。唯一能解决“需要以non-member函数访问class的non-public成分”的办法就是:弱化class的封装。例如class可以声明那个non-member函数为friends,或是为其实现的某一部分提供public访问函数。利用函数指针替换virtual函数,其优点(像是“每个对象可各自拥有自己的健康计算函数”和“可在运行期间改变计算函数”)是足以弥补缺点(例如可能必须降低GameCharacter封装性)。


3.藉由tr1::function完成Strategy模式
我们不再用函数指针,而是用一个类型为tr1::function的对象,这样的对象可持有(保存)任何可调用物(callable entity,也就是函数指针、函数对象、或成员数函数指针),只要其签名式兼容于需求端。
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
//HealthCalcFunc可以是任何“可调用物”,可被调用并接受
//任何兼容于GameCharacter之物,返回任何兼容于int的东西。
typedef std::tr1::function HealthCalcFunc;
explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
: healthFunc(hcf)
{}
int healthValue() const
{
return healthFunc(*this);
}
...
private:
HealthCalcFunc healthFunc;
};
这里我们把tr::function的具现体的目标签名式以不同颜色强调出来。那个签名代表的函数是“接受一个reference指向const GameCharacter,并返回int”
std::tr1::function
所谓兼容,意思是这个可调用物的参数可被隐式转换为const GameCharacter&,而其返回类型可被隐式转换成int。
客户在“指定健康计算函数”这件事上有更惊人的弹性:
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