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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值