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

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

  • 相关阅读:
    什么是webview
    juqery.fn.extend和jquery.extend
    LeetCode
    5. Longest Palindromic Substring
    42. Trapping Rain Water
    11. Container With Most Water
    621. Task Scheduler
    49. Group Anagrams
    739. Daily Temperatures
    3. Longest Substring Without Repeating Characters
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785439.html
Copyright © 2011-2022 走看看