zoukankan      html  css  js  c++  java
  • 【effective c++读书笔记】【第6章】继承与面向对象设计(2)

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

    假设你在开发一款游戏,你打算为游戏内的人物设计一个继承体系。每个人物都有自己的健康状态。你能马上想到下面这个设计:

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

    有没有其他替代方案呢?答案是肯定的。

    1、藉由Non-Virtual Interface手法实现Template Method模式

    //GameCharacter.h
    #ifndef GAMECHARACTER_H
    #define GAMECHARACTER_H
    
    class GameCharacter{
    public:
    	int healthValue() const;
    private:
    	virtual int getInitialValue() const;
    	virtual int doHealthValue(int healthValue) const;
    };
    int GameCharacter::healthValue() const{
    	int retVal = getInitialValue();
    	retVal = doHealthValue(retVal);
    	return retVal;
    }
    int GameCharacter::getInitialValue() const{
    	return 100;
    }
    int GameCharacter::doHealthValue(int healthValue) const{
    	return healthValue;
    }
    
    #endif
    
    //EvilBadGuy.h
    #ifndef EVILBADGUY_H
    #define EVILBADGUY_H
    
    #include"GameCharacter.h"
    
    class EvilBadGuy:public GameCharacter{
    private:
    	virtual int getInitialValue() const;
    	virtual int doHealthValue(int healthValue) const;
    };
    int EvilBadGuy::getInitialValue() const{
    	return 400;
    }
    int EvilBadGuy::doHealthValue(int healthValue) const{
    	return healthValue / 2;
    }
    
    #endif
    
    //main.cpp
    #include"EvilBadGuy.h"
    #include<iostream>
    using namespace std;
    
    int main(){
    	EvilBadGuy badGuy;
    	cout << badGuy.healthValue() << endl;
    
    	system("pause");
    	return 0;
    }
    

    在基类GameCharacter中healthValue成员函数调用了doHealthValue函数,这种”令客户通过public non-virtual成员函数间接调用private virtual函数“,称为non-virtualinterface(NVI)手法。这个non-virtual(healthValue)函数称为virtual(doHealthValue)函数的外覆器(wrapper)。

    NVI手法的一个优点是外覆器确保得以在一个virtual函数被调用之前设定好适当场景,并在调用结束之后清理场景。NVI手法允许继承类重新定义virtual函数,从而赋予它们”如何实现机能“的控制能力,但基类保留”函数何时被调用“的权利。

    2、藉由Function Pointers实现Strategy模式

    //GameCharacter.h
    #ifndef GAMECHARACTER_H
    #define GAMECHARACTER_H
    
    class GameCharacter;
    int defaultHealthCalc(const GameCharacter& gc);
    
    class GameCharacter{
    public:
    	typedef int(*HealthCalcFunc)(const GameCharacter&);
    	explicit GameCharacter(HealthCalcFunc hfc = defaultHealthCalc) :healthFunc(hfc){}
    	int healthValue() const { return healthFunc(*this); }
    	virtual int getInitialValue() const { return 50; }
    private:
    	HealthCalcFunc healthFunc;
    };
    
    int defaultHealthCalc(const GameCharacter& gc){
    	return gc.getInitialValue();
    }
    
    #endif
    
    //EvilBadGuy.h
    #ifndef EVILBADGUY_H
    #define EVILBADGUY_H
    
    #include"GameCharacter.h"
    
    class EvilBadGuy :public GameCharacter{
    public:
    	explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) :GameCharacter(hcf){}
    	virtual int getInitialValue() const{ return 400; }
    };
    
    int loseHealthQuickly(const GameCharacter& gc){
    	int retVal = gc.getInitialValue();
    	retVal /= 4;
    	return retVal;
    }
    
    int loseHealthSlowly(const GameCharacter& gc){
    	int retVal = gc.getInitialValue();
    	retVal /= 2;
    	return retVal;
    }
    
    #endif
    
    //main.cpp
    #include"EvilBadGuy.h"
    #include<iostream>
    using namespace std;
    
    int main(){
    	EvilBadGuy badGuy1(loseHealthQuickly);
    	cout << badGuy1.healthValue() << endl;
    
    	EvilBadGuy badGuy2(loseHealthSlowly);
    	cout << badGuy2.healthValue() << endl;
    
    	system("pause");
    	return 0;
    }
    

    这个做法是Strategy设计模式的简单应用,它提供了某些有趣弹性:

    a、同一人物类型的不同实体可以有不同的健康计算函数,如main函数中的badGuy1和badGuy2。

    b、某已知人物的健康指数计算函数可在运行期变更,如GameCharacter可提供一个成员函数setHealthCalculator,用来替换当前的健康指数计算函数。

    3、藉由tr1::function完成Strategy模式

    //GameCharacter.h
    #ifndef GAMECHARACTER_H
    #define GAMECHARACTER_H
    
    #include<functional>
    
    class GameCharacter;
    short defaultHealthCalc(const GameCharacter& gc);//注意其返回类型不是int
    
    class GameCharacter{
    public:
    	typedef std::function<int(const GameCharacter&)> HealthCalcFunc;
    	explicit GameCharacter(HealthCalcFunc hfc = defaultHealthCalc) :healthFunc(hfc){}
    	int healthValue() const { return healthFunc(*this); }
    	virtual int getInitialValue() const { return 50; }
    private:
    	HealthCalcFunc healthFunc;
    };
    
    short defaultHealthCalc(const GameCharacter& gc){
    	return gc.getInitialValue();
    }
    
    #endif
    
    //EvilBadGuy.h
    #ifndef EVILBADGUY_H
    #define EVILBADGUY_H
    
    #include"GameCharacter.h"
    
    class EvilBadGuy :public GameCharacter{
    public:
    	explicit EvilBadGuy(HealthCalcFunc hcf = defaultHealthCalc) :GameCharacter(hcf){}
    	virtual int getInitialValue() const{ return 400; }
    };
    
    struct LoseHealthQuickly{
    	int operator()(const GameCharacter& gc) const{
    		int retVal = gc.getInitialValue();
    		retVal /= 4;
    		return retVal;
    	}
    };
    
    class GameLevel{
    public:
    	float loseHealthSlowly(const GameCharacter& gc) const{//注意返回类型不是int
    		float retVal = static_cast<float>(gc.getInitialValue());
    		retVal /= 2;
    		return retVal;
    	}
    };
    
    #endif
    
    //main.cpp
    #include"EvilBadGuy.h"
    #include<iostream>
    using namespace std;
    
    int main(){
    	EvilBadGuy badGuy1;//默认普通函数计算健康指数
    	cout << badGuy1.healthValue() << endl;
    
    	LoseHealthQuickly loseHealthQuickly;//函数对象计算健康指数
    	EvilBadGuy badGuy2(loseHealthQuickly);
    	cout << badGuy2.healthValue() << endl;
    
    	GameLevel currentLevel;//成员函数计算健康指数
    	EvilBadGuy badGuy3(bind(&GameLevel::loseHealthSlowly, currentLevel, placeholders::_1));
    	cout << badGuy3.healthValue() << endl;
    
    	system("pause");
    	return 0;
    }
    

    这个做法与前面一个例子的区别:没有定义类型确定的函数指针,而是定义了一个类型为tr1:: function的对象。它的返回值为int或可转换为int的类型,输入参数为constGameCharacter引用或可以转化为constGameCharacter引用的类型。这样的对象可持有任何可调用物,即可以是一般函数指针、函数对象或成员函数指针,只要其签名式兼容于需求端。

    对非静态成员函数,需要通过bind绑定。为了计算badGuy3的健康函数,需要使用GameLevel里面的loseHealthSlowly函数,这个函数实际上有两个参数:*this和GameCharacter&,而HealthCalcFunc只接受一个参数:GameCharacter&。需要将GameLevel类型中的loseHealthSlowly函数与调用它的对象绑定起来,此后每次调用loseHealthSlowly函数,都是调用绑定的那个对象的loseHealthSlowly函数。其中_1是占位符,表示的是这个函数的第一个参数。

    4、古典的Strategy模式

    //GameCharacter.h
    #ifndef GAMECHARACTER_H
    #define GAMECHARACTER_H
    
    #include"HealthCalcFunc.h"
    
    extern HealthCalcFunc defaultHealthCalc;
    class GameCharacter{
    public:
    	explicit GameCharacter(HealthCalcFunc* phcf = &defaultHealthCalc) :pHealthFunc(phcf){}
    	int healthValue() const { return pHealthFunc->calc(*this); }
    	virtual int getInitialValue() const { return 50; }
    private:
    	HealthCalcFunc* pHealthFunc;
    };
    
    #endif
    
    //EvilBadGuy.h
    #ifndef EVILBADGUY_H
    #define EVILBADGUY_H
    
    #include"GameCharacter.h"
    
    class EvilBadGuy :public GameCharacter{
    public:
    	explicit EvilBadGuy(HealthCalcFunc* phcf = &defaultHealthCalc) :GameCharacter(phcf){}
    	virtual int getInitialValue() const{ return 400; }
    };
    
    #endif
    
    //HealthCalcFunc.h
    #ifndef HEALTHCALCFUNC_H
    #define HEALTHCALCFUNC_H
    
    class GameCharacter;
    class HealthCalcFunc{
    public:
    	virtual int calc(const GameCharacter& gc) const;
    };
    
    class LoseHealthQuickly :public HealthCalcFunc{
    public:
    	virtual int calc(const GameCharacter& gc) const;
    };
    
    class LoseHealthSlowly :public HealthCalcFunc{
    public:
    	virtual int calc(const GameCharacter& gc) const;
    };
    
    
    #endif
    
    //HealthCalcFunc.cpp
    #include"GameCharacter.h"
    
    int HealthCalcFunc::calc(const GameCharacter& gc) const{
    	int retVal = gc.getInitialValue();
    	return retVal;
    }
    int LoseHealthQuickly::calc(const GameCharacter& gc) const{
    	int retVal = gc.getInitialValue();
    	retVal /= 4;
    	return retVal;
    }
    int LoseHealthSlowly::calc(const GameCharacter& gc) const{
    	int retVal = gc.getInitialValue();
    	retVal /= 2;
    	return retVal;
    }
    HealthCalcFunc defaultHealthCalc;
    
    //main.cpp
    #include"EvilBadGuy.h"
    #include<iostream>
    using namespace std;
    
    int main(){
    	EvilBadGuy badGuy1;
    	cout << badGuy1.healthValue() << endl;
    
    	LoseHealthQuickly loseHealthQuickly;
    	EvilBadGuy badGuy2(&loseHealthQuickly);
    	cout << badGuy2.healthValue() << endl;
    
    	LoseHealthSlowly loseHealthSlowly;
    	EvilBadGuy badGuy3(&loseHealthSlowly);
    	cout << badGuy3.healthValue() << endl;
    
    	system("pause");
    	return 0;
    }
    

    这个解法的吸引力在于熟悉标准Strategy模式的人很容易辨认它,可扩展性很强,可以加入不同的角色,只要从GameCharacter中派生即可,也可以加入新的健康算法,只要从HealthCalcFunc中派生即可。这个方法称为strategy模式,它的定义如下:Strategy模式定义了一系列的算法,将它们每一个进行封装,并使它们可以相互交换。Strategy模式使得算法不依赖于使用它的客户端。

    对于虚函数,有以下几种替代方案:

    a、 使用non-virtualinterface(NVI)手法。

    b、将virtual函数替换为“函数指针成员变量“。这是Strategy设计模式的一种分解表现形式。

    c、以tr1::function成员变量替换virtual函数。这也是Strategy设计模式的某种形式。

    d、将继承体系内的virtual函数替换为另一个继承体系内的virtual函数。这是Strategy设计模式的传统实现方法。

    请记住:

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

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    poj 3928 树状数组
    poj 2528 线段树+离散化
    poj 3468 成段增减
    hdu 2795 线段树(纵向)
    2016年江西理工大学C语言程序设计竞赛(高级组)
    2016年江西理工大学C语言程序设计竞赛(初级组)
    2015年江西理工大学C语言程序设计竞赛(高级组)
    江西理工大学南昌校区2016年新生赛
    2014江西理工大学C语言程序竞赛高级组
    2014江西理工大学C语言程序竞赛初级组
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785439.html
Copyright © 2011-2022 走看看