zoukankan      html  css  js  c++  java
  • Effective C++:条款35:考虑virtual函数以外的其它选择

    游戏中的人物伤害值计算问题。

    (一)方法(1):一般来讲能够使用虚函数的方法:

    class GameCharacter {  
        public:  
            virtual int healthValue() const;    //返回人物的体力值,派生类能够做出改动  
            ...  
    };  
    这确实是一个显而易见的设计选择。但由于这种设计过于显而易见,可能不会对其他可选方法给予足够的关注。我们来考虑一些处理这个问题的其他方法。


    (二)方法(2):使用NVI方法,在基类中使用一个公有的普通函数调用私有的虚函数。

    class GameCharacter{  
        public:  
            int healthValue() const {       //派生类不能又一次定义它  
                ...                         //做一些事前工作  
                int retVal = doHealthValue();       //调用私有函数进行计算  
                ...                        //做一些事后工作  
                return retVal;  
            }  
        private:  
            virtual int doHealthValue() const{      //派生类能够又一次定义  
                ...         //提供缺省算法  
            }  
    };  
            NVI手法的一个优势通过 "做事前工作" 和 "做事后工作" 两个凝视在代码中标示出来。这意味着那个外覆器能够确保在virtual函数被调用前,特定的背景环境被设置,而在调用结束后,这些背景环境被清理。比如,事前工作能够包含锁闭一个mutex,生成一条日志,校验类变量和函数的先决条件是否被满足,等等。事后工作能够包含解锁一个mutex,校验函数的事后条件,再次验证类约束条件,等等。假设你让客户直接调用virtual函),确实没有好的方法能够做到这些。
          NVI手法事实上不是必需让virtual函数一定是private。有时必须是protected(在继承体系中,子类要直接调用基类成员函数)。还有时候甚至是public,这么一来的话就不能实施NVI手法了。


    (三)方法(3)使用函数指针。

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

    这样的方法的长处是它可以:

    (1)通过定义不同的体力值计算方法,同种类型的人物通过调用不同的函数能够实现不同的计算方法:

    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);//不同的健康计算方式

    (2)人物的体力计算方法能够在执行期间变更(相当于为GameCharacter的私有变量又一次赋值)。

    比如GameCharacter可提供一个成员函数setHealthCalculator,用来替换当前的健康指数计算函数。


    使用函数指针这样的方法(包含以后的两种方法)可能会使用类外的函数,从而减少封装性。所以在用这样的方法的时候,他的上面两种长处是否能弥补他的缺点(减少类的封装性)是我们在整个设计之前须要考虑的东西。


    (四)方法(4)使用tr1::function完毕Strategy模式。

    class GameCharacter;  
    int defaultHealthCalc(const GameCharacter& gc);  
    class GameCharacter {  
        public:  
            //HealthCalcFunc能够是不论什么“可调用物”,可被调用并接受不论什么兼容于GameCharacter之物,返回不论什么兼容于int的东西,详下:  
            typedef std::tr1::function<int (const GameCharacter&)> HealthCalcFunc;  
            //这样的定义表示HealthCalcFunc作为一种类型,接受GameCharacter类型的引用,并返回整数值,当中支持隐式类型转换  
            explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf)  
            {}  
            int healthValue() const{  
                return healthFunc(*this);  
            }  
            ...  
        private:  
            HealthCalcFunc healthFunc;  
    };  

    那个签名代表的函数是“接受一个reference指向const GameCharacter,并返回int”

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

    所谓兼容,意思是这个可调用物的參数可被隐式转换为const GameCharacter&,而其返回类型可被隐式转换成int。

    在这里,GameCharacter持有一个tr1::function对象,相当于一个指向函数的泛化指针。

    在使用这种方法时P175介绍了三种调用方式,即使用三种方式初始化GameCharacter的派生类:一个详细的函数,一个函数对象,以及一个像std::tr1::bind(&GameLevel::health, currentLevel, _1)这样用一个对象的成员函数。

    EvilBadGuy ebg1(calcHealth);        //使用某个函数  
    EyeCandyCharacter ecc1(HeathCalculator());      //使用某个函数对象(包括一个函数的结构体)  
    GameLevel currentLevel;  
    EvilBadGuy ebg2(std::tr1::bind(&GameLevel::health, currentLevel, _1));  
    完整代码像这样:

    客户在“指定健康计算函数”这件事上有更惊人的弹性:

    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);//函数 
    EyeCandyCharacter ecc1(HealthCalculator());//函数对象 
    GameLevel currentLevel; 
    ... 
    EvilBadGuy ebg2(std::tr1::bind(&GameLevel::health, currentLevel, _1));//成员函数
    GameLevel::health宣称它接受两个參数,但实际上接受两个參数,由于它也获得一个隐式參数GameLevel,也就是this所指的那个。然而GameCharacter的健康计算函数仅仅接受单一參数:GameCharacter。假设我们使用GameLevel::health作为ebg2的健康计算函数,我们必须以某种方式转换它,使它不再接受两个參数(一个GameCharacter和一个GameLevel),转而接受单一參数(GameCharacter)。于是我们将currentLevel绑定为GameLevel对象,让它在“每次GameLevel::health被调用以计算ebg2的健康”时被使用。那正是tr1::bind的作为。


    (五)方法(5)使用古典的Strategy模式

    将健康计算函数做成一个分离的继承体系中的virtual成员函数。

    class GameCharacter; 
    class HealthCalcFunc { 
        ... 
        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; 
    };

    每个GameCharacter对象都内含一个指针,指向一个来自HealthCalcFunc继承体系的对象。

    还能够提供“将一个既有的健康计算算法纳入使用”的可能性--仅仅要为HealthCalcFunc继承体系加入一个derived class就可以。

    UML图在书上P176。


    (六)总结:

    虚函数的替代方案有:

    (1)使用non-virtual interface(NVI)方法,它是Template Method设计模式的一种特殊形式。使客户通过仅有的非虚函数间接调用私有的虚函数,该公有的非虚函数称为私有虚函数的“外覆器”(wrapper)。公有的非虚函数能够在调用虚函数前后做一些其它工作(如相互排斥器的锁定与解锁,验证约束条件等)。

    (2)将虚函数替换为“函数指针成员变量”,它是Strategy设计模式的一种分解表现形式。

    (3)以tr1::function成员变量替换虚函数,从而同意使用不论什么可调用物搭配一个兼容于需求的签名式(这句话表达太晦涩了,非常难理解,样例见下)。它也是Strategy设计模式的某种形式。

    (4)将继承体系内的虚函数替换为还有一个继承体系内的虚函数,这是Strategy设计模式的传统实现手法。


    请记住:

    (1)virtual函数的替代方案包含NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。

    (2)将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法訪问class的non-public成员。

    (3)tr1::function对象的行为就像一般函数指针。这种对象可接纳“与给定之目标签名式(target signature)兼容”的全部可调用物(callable entities)。   






  • 相关阅读:
    Suricata 1.3.3 发布,入侵检测系统
    Blue Mind 1.0 正式版发布,消息和协作平台
    Ceylon M4 和 Ceylon IDE M4 发布
    微软正式开放 Windows Phone 8 SDK 下载
    SchemaCrawler 9.3 发布
    ASP.NET防伪令牌与JSON有效载荷
    WinRT控件:图表、图示、地图和其他
    dhtmlxSpreadsheet 2.0 发布,表格生成工具
    Funcito 1.2.0 发布,Java 函数式编程库
    Rabel 1.3.9 发布,让论坛回归交流本质
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4062154.html
Copyright © 2011-2022 走看看