C++之考虑virtual函数以外的其他选择(35)---《Effective C++》

条款35:考虑virtual函数以外的其他选择

我们想要编写一个游戏软件,为游戏内部的任务设计一个继承体系,我们需要计算每个任务的血量,因此设计成员函数healthValue,由于不同人物的血量计算方式不同,因此需要将healthValue声明为virtual啦!healthValue并未被声明为pure virtual,暗示healthValue方法有默认的缺省算法。

class GameCharacter{
public:
    virtual int healthValue() const;
    ...
};

这是最简单的设计,但可能成为它的弱点,由于这个设计如此明显,可能并不会认真考虑完整的替代方案,为了扩展思路,跳脱常轨,让我们考虑一些其他算法!

摘要:
1)使用non-virtual interface(NVI)手法,那是Template Method设计模式的一种特殊形式,它以public non-virtual成员函数包裹较低访问性(private或者protected)的virtual函数;
2)将virutal函数替换为“函数指针成员变量”,这是Strategy设计模式的一种分解表现形式;
3)以trl::function成员变量替换virtual函数,因而允许使用任何可调用误(callable entity)搭配一个兼容与需求的签名式,这也是Strategy设计模式的某种形式;
4)将继承体系内的virtual函数替换为另一个继承体系中的virtual函数,这是Strategy设计模式的传统实现手法。

1. 借由Non-Virtual Interface(NVI)手法实现Templated Method模式

class GameCharacter{
public:
    int healthValue()const{
        ...
        int retVal=doHealthValue();
        ...
        return retVal;
    }
    ...
private:
    virtual int doHeatlthValue() const{
        ...
    }
};

这种设计,“令用户通过public non-virtual方法调用private virtual函数”,称为non-virtual interface(NVI)手法,这是所谓的Template Method设计模式(与C++ templates并无关联)的一个独特表现形式,我把这个non-virtual函数(healthValue)称为virtual函数的外覆器(wrapper)。

2. 借由Function Pointer实现Strategy模式

class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter{
public:
    typedef int(*HealthCalcFunc) (const GameCharacter);
    explicit GameCharacter(HealthCalcFunc hfc=defaultHealthCalc):health(hcf)
    {}
    int healthValue() const{
        return healthFunc(*this);
    }
    ...
private:
    HealthCalcFunc healthFunc;
};

这个做法是Strategy设计模式的额简单引用,拿它和“植基于GameCharacter继承体系之virtual函数”的做法比较,它提供了某些有趣弹性:
1)同一人物类型之间的不同实体可以有不同的健康计算函数,如:

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);

2)某已知人物之甲亢支书计算函数可在运行期变更,例如GameCharacter可以提供一个成员函数setHealthCalculator,用来替换当前的支书计算函数。

唯一能够解决“需要以non-member函数访问class 的non-public成分”的办法就是:弱化class的封装。例如class可以声明那个non-member函数为friends,或是为其实现的某一部分提供的public访问函数(其他部分则宁可各自拥有自己的健康计算函数和“可在运行期间改变的计算函数”)是否足以弥补缺点(例如可能必须降低GameCharacter的封装性),是你必须更具每个设计情况的不同而需要加以抉择的。

3. 借由trl::function完成Strategy模式

一旦习惯了templates以及他们对隐式接口的使用,基于函数指针的做法看起来便过分苛刻死板了,为什么要求“HealthCalcFunc”必须是个函数,而不能是某种“像函数的东西”呢?如果一定需要是函数,威慑么不能够是个成员函数?威慑么一定得返回int而不是任何可以被转换为int的类型呢?
如果我们不再使用函数指针,改用一个类型为trl::function的对象那个,这些约束就全都会发不见了,这样的对象可以持有(保存)任何可调用物(callable entity,也就是函数指针、函数对象或者成员函数指针),只要其签名式兼容于需求端,以下将刚才的设计改为使用trl::function:

class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter{
public:
    typedef std::trl::function<int (const GameCharacter&> HealthCalcFunc;
    explicit GameCharacter(HealthCalcFunc hcf=defaultHealthCalc):heathFunc(hcf)
    {}
    int healthValue() const
    {
        return healthFunc(*this);
    }
    ...
private:
    HealthCalcFunc healthFunc;
};

此时HealthCalcFunc是个typedef,用来表现trl::function这个具现体,意味着具现体的行为像一般的函数指针,现在让我们瞅瞅HealthCalcFunc是个怎样的typedef:

std::trl::function<int  (const GameCharacter&)>

这里我们将trl::function具现体的目标签名式(target signature)以不同的颜色强调出来,这个签名代表的函数是“接受一个reference指向const的GameCharacter,并返回int”,这个trl::function类型(也就是这里的HealthCalcFunc类型)产生的对象那个可以保存任何与此签名式兼容的可调用物(callable entity,即函数指针、函数引用或者成员函数引用),所谓兼容,意思就是这个可调用误的参数可以被隐式转换为const GameCharacter&,而其返回类型可被隐式转换为int。这种设计相较与前一个设计只是让GameCharacter保存一个trl::function对象,相当于一个指向函数的泛华指针,这个改变如此细小,我总说它没有任何外显影响,除非客户在“指定健康计算函数”这件事情上面需要更惊人的弹性。

short calcHeatlth(const GameCharacter&);

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 eccl(healthCalculator());
GameLevel currentLevle;
...
EvilBadGuy ebg2(std::trl::bind(&GameLevel::health,currentLevle,_1));

4. 古典的Strategy模式

这里写图片描述
上图表示的是GameCharacter作为基类,EvilBadGuy和EyeCandyCharacter作为GameCharacter的派生类,同时HealthCalcFunc作为基类,SlowHealthLoser和FastHealthLoser作为HealthCalcFunc,然后GameCharacter中有一个HealthCalcFunc指针的成员变量,看看如下代码:

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;
};

这种设计方法的吸引力在于,熟悉标准的Strategy模式的人很容易辨认它,而且它还提供“讲一个即有的健康算法纳入使用”的可能性—只要为HealthCalc继承体系中添加一个derived class即可。

总结:

1)virtual函数的替代方案包括NVI手法以及Strategy设计模式的多种形式,NVI手法自身是一个特殊形式的Template Method设计模式;
2)将机能从成员函数转移到class外部函数,带来的一个缺点是:非成员函数无法访问class 的non-public成员;
3)trl::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物(callable entities)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值