条款35:考虑virtual函数以外的其他选择
1、考虑virtual函数以外的其他选择是什么意思?
大多时候,我们会自然而然的想到使用virtual手法来塑模现实中的类。但是,实际上也有别的方案可以替代virtual手法的,即:考虑virtual函数以外的其他选择。下面介绍的便是几种可以替代virtual的方案。
2、第一种方案:通过Non-Virtual Interface (NVI)手法实现Template Method 模式
(1)Non-Virtual Interface 是Template Method 设计模式的一个独特的表现形式。
class GameCharacter {
public:
//1、派生类不可改变的算法骨架
int healthValue() const
{
...//做一些事前工作
int retVal = doHealthValue();//核心点
...//做一些事后工作
return retVal;
}
private:
//2、派生类可以改变的算法细节
virtual int doHealthValue() const //derived class 可以重新定义它。
{
...//缺省计算,计算健康指数
}
};
(2)这里把non-virtual的函数 healthValue()称为doHealthValue()函数的外覆器。
这个实现方法的优点:可以在核心点前做准备工作,和核心点后做善后工作。
(3)关于对派生类对象中重新定义 private virtual函数的担心
- 派生类有权利决定实算法的某一步的具体细节
- 派生类无权决定派生类实现的步骤和时机(这些由基类决定。)
- 这是合情合理的,因此不必担心。
(4)virtual函数的访问控制
- NVI手法中,virtual函数可以是private的
- NVI手法中,virtual函数也可以是protected的。这样的写法使得派生类可以调用其父类中的对应的函数。
- NVI手法中,virtual函数不可以是public的。
注意:准备工作可以是:锁定互斥器、验证class约束条件、验证函数先决条件等等。善后工作可以是:解除互斥器锁定、验证函数的事后条件、再次验证class约束条件等等。
3、第二种方案:通过Function Pointers完成Strategy模式
(1)具体做法
在游戏角色类中添加一个指针,指向一个计算健康指数的函数。
(2)优点
通过Funtion Pointers完成Strategy与使用virtual函数实现的比较:
- 同一人物类型的不同实体可以有不同的健康计算函数。
- 某个已知人物的健康指数极端函数可以在运行期变更。
4、第三种方案:通过tr1::function完成Strategy模式(使用函数对象,而不是使用函数指针)
(1)实现方法:
上面的方法是让游戏角色类拥有一个指向函数的指针。而这个方案是让游戏角色类拥有一个像函数一样的函数对象。
(2)优点:
- 这种方案比上述方案的效果更好,因为函数对象允许我们做的事更多。可以调用任何兼容函数计算健康指数。
5、第四种方案:传统的Strategy模式
(1)传统的Strategy的做法是:
- 设计一个游戏角色类的继承体系,其中可以派生多个类。并且在其基类中包含一个指向计算健康指数类的指针。
- 将计算健康指数设计成一个继承体系,其中可以派生多个类。
(2)优点:
- 设计模式容易辨认。
- 增加新的健康指数计算类比较容易,只需要在健康指数继承体系中派生一个新的计算类即可。
6、二、三、四方案存在的问题
(1)实现方案存在的问题
实际上也就是说,健康指数计算函数不再是游戏角色类的成员函数了,这也意味着他们不能直接访问游戏角色类的成员了。
(2)解决上述问题的方法:
弱化游戏角色类的封装,这也是这种替代方案的缺点,这是下面Strategy设计模式都要需要面临的问题。
方案一:游戏角色类可以将该函数声明为友元。
方案二:将游戏角色类的某些成员声明为public。
(3)三种方案的缺点
弱化了封装。