1. 非虚拟接口惯用法(NVI 惯用法)
- 设计思路:主张将虚拟函数设为私有,通过公有非虚拟成员函数调用私有虚拟函数来完成实际工作。例如在游戏角色类中:
class GameCharacter {
public:
int healthValue() const {
// 执行“前”操作,如锁闭互斥体、生成日志等
int retVal = doHealthValue();
// 执行“后”操作,如解锁互斥体、校验后置条件等
return retVal;
}
private:
virtual int doHealthValue() const {
// 默认健康值计算算法
}
};
- 优势:公有非虚拟函数(如
healthValue
)作为虚拟函数的外壳,可确保在调用虚拟函数前后设置和清理特定背景环境,如进行锁操作、日志记录、条件校验等。虽然通常虚拟函数设为私有,但在某些情况下(如派生类需调用基类对应函数),虚拟函数可为保护或公有,但此时 NVI 惯用法不能完全应用。
2. 经由函数指针实现的策略模式
- 设计思路:通过为角色构造函数传递指向健康值计算函数的指针,来计算角色健康值。
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;
};
- 优势:相同角色类型的不同实例可拥有不同健康值计算函数,且可在运行时改变。例如:
class EvilBadGuy : public GameCharacter {
public:
explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) : GameCharacter(hcf) {}
};
int loseHealthQuickly(const GameCharacter&);
int loseHealthSlowly(const GameCharacter&);
EvilBadGuy ebg1(loseHealthQuickly);
EvilBadGuy ebg2(loseHealthSlowly);
- 劣势:健康值计算函数不再是
GameCharacter
类的成员函数,无法访问对象的非公有构件。若健康值计算依赖非公有信息,可能需削弱类的封装性,如声明非成员函数为友元或提供公有访问函数。
3. 经由tr1::function
实现的策略模式
- 设计思路:用
tr1::function
类型对象代替函数指针,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;
};
- 示例:
short calcHealth(const GameCharacter&);
、struct HealthCalculator { int operator()(const GameCharacter&) const {... } };
、class GameLevel { public: float health(const GameCharacter&) const {... } };
等不同类型的可调用实体都可用于计算健康值。例如:
EvilBadGuy ebg1(calcHealth);
EyeCandyCharacter ecc1(HealthCalculator());
GameLevel currentLevel;
EvilBadGuy ebg2(std::tr1::bind(&GameLevel::health, currentLevel, _1));
- 优势:相比基于函数指针的方法,客户在指定健康值计算函数时有更大灵活性,可使用任何兼容的可调用实体。
4. “经典的” 策略模式
- 设计思路:将健康值计算函数做成一个独立的继承体系的虚拟成员函数,
GameCharacter
类包含一个指向该继承体系派生对象的指针。
class GameCharacter;
class HealthCalcFunc {
public:
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;
};
- 优势:熟悉标准策略模式的人易于识别,且可通过在
HealthCalcFunc
继承体系中增加派生类来微调健康值计算算法。
5. 总结
在设计时应考虑虚拟函数的替代方法,如 NVI 惯用法和策略模式的多种变体。NVI 惯用法是模板方法模式的实例。将机能从成员函数移到类外函数可能导致非成员函数无法访问类的非公有成员。tr1::function
对象类似泛型化的函数指针,支持所有与给定目标特征兼容的可调用实体。这些替代方法各有优劣,设计时应权衡考虑,避免局限于传统的面向对象设计方式。