C++编程杂谈之三:面向工具(续)
副标题#e#
上一篇我们涉及了面向工具的一个根基观念–封装,封装是一个相比拟力简朴的观念,也很容易接管,可是许多的场所下面,仅仅是封装并不能很好的办理许多问题,思量下面的例子:
假设我们需要设计一个对战游戏的战斗细节,在最初的版本中我们将支持一种行动–fight。假设我们有三种脚色:fighter、knight和warrior,每种脚色的health、hit point差异,基于封装的根基想法,我们很自然的想到对每个工具利用类的封装,首先明明的元素有2个:health、hit point,别的尚有name(我们不能只有三个脚色)和战斗的速度speed,要领有:hit、isalive。基于这样的想法,我们给出下面的类:
class fighter
{
private:
int m_iHealth;
int m_iSpeed;//为了利便,这个值利用延时值,越大暗示速度越慢
const int m_iHitPoint;
char m_strName[260];
public:
fighter(const char* strName);
void hit(fighter* pfighter);
bool isAlive();
};
上面的类可以清楚的抽象出我们所需要表达的数据范例之一,这里也许你已经发明白一些问题:
成员函数hit用来处理惩罚战斗事件,可是我们有差异的脚色,fighter不行能只跟本身同样的敌手作战,我们但愿它能和任何脚色战斗,虽然,利用模板函数可以简朴的办理这个问题。别的,我们必需去实现三个差异的类,而且这些类必需都实现这些属性和要领。纵然这些问题我们都办理了,此刻我们要组织两个步队作战,我们但愿利用一种群体范例来描写它们,问题是我们必需针对每一种类成立相应的群体布局,虽然你可以认为3个差异的范例不是许多,完全可以应付,那么假如有一天系统进级,你需要打点上百种范例的时候,会不会头大呢?
在C++中,担任就可以很好的办理这个问题,在C++中,担任暗示的是一种IS-A干系,即派生类IS-A基类,许多现实世界中的干系可以这样来描写,如:dog is-a animal,dog是animal的派生(担任),担任发生的工具拥有父(基)工具的所有属性和行为,如animal的所有属性和行为在dog身上城市有相应的表示。在UML的描写中,这种干系被称为泛化(generalization)。一般环境下,当我们需要实现一系列相似(具有必然的共性)然而有互相差异的类此外时候,利用担任都是很好的办理步伐,譬喻前面的代码固然也可以或许实现我们的方针,可是显然很难打点,下面给出利用担任后的实现:
class actor//基类
{
protected:
int m_iHealth;
const int m_iSpeed;//为了利便,这个值利用延时值,越大暗示速度越慢
const int m_iHitPoint;
char m_strName[260];
public:
actor(const char* strName,const int iHealth,const int iSpeed,const int iHitpoint);
int& Health(){ return m_iHealth; };
const char* getName(){ return m_strName; };
virtual void hit(actor *Actor) = 0;
bool isAlive();
};
actor::actor(const char* strName,const int iHealth,const int iSpeed,const int iHitpoint):
m_iHealth(iHealth),
m_iSpeed(iSpeed),
m_iHitPoint(iHitpoint)
{
strcpy(m_strName,strName);
}
bool actor::isAlive()
{
return (m_iHealth>0);
}
/////////////////////////////////////////////////////////
//类fighter
class fighter :public actor
{
public:
fighter(const char* strName);
virtual void hit(actor *Actor);
private:
};
fighter::fighter(const char* strName):
actor(strName,100,20,20)
{
}
void fighter::hit(actor *Actor)
{
Sleep(m_iSpeed);
if(!isAlive())
{
return ;
}
if(Actor&&Actor->isAlive())
{
//这里我们用一个函数做了左值,因为函数返回的是引用
if(isAlive())
Actor->Health() = Actor->Health() - m_iHitPoint;
}
}
/////////////////////////////////////////////////////////
//类knight
class knight :public actor
{
public:
knight(const char* strName);
virtual void hit(actor *Actor);
private:
};
knight::knight(const char* strName):
actor(strName,150,20,25)
{
}
void knight::hit(actor *Actor)
{
Sleep(m_iSpeed);
if(!isAlive())
{
return ;
}
if(Actor&&Actor->isAlive())
{
if(isAlive())
Actor->Health() = Actor->Health() - m_iHitPoint;
}
}
/////////////////////////////////////////////////////////
//类warrior
class warrior :public actor
{
public:
warrior(const char* strName);
virtual void hit(actor *Actor);
private:
};
warrior::warrior(const char* strName):
actor(strName,150,20,25)
{
}
void warrior::hit(actor *Actor)
{
Sleep(m_iSpeed);
if(!isAlive())
{
return ;
}
if(Actor&&Actor->isAlive())
{
if(isAlive())
Actor->Health() = Actor->Health() - m_iHitPoint;
}
}
#p#副标题#e#
#p#分页标题#e#
C++为我们提供了很是优秀的担任体系(其实这是面向工具的一个很是重要的特征),上面的类图中我们可以很清楚的看到他们的干系,在担任中,基类(有些处所也称为超类)其实是所有派生类的共性提炼的功效,有时候在开拓进程中,是先有派生类,然后再提出共性,发生基类的。
就象遗传一样,派生类拥有基类的所有属性和要领,如基类actor的成员函数和成员变量在每一个派生类中都存在,即fighter、knight和warrior中都存在,在担任干系中还存在一个很是重要的要害字protected,它提供一个界于public和private 之间的一种可见性,与private沟通的处所是对付外部来说,它不行见,与public沟通的处所是对与担任体系来说,是向下可见的(其派生出来的类可以直接利用),这里有一点是很容易让人疑惑的,就是派生类中基类的成员可见性。对派生类来说,假如利用public担任,那么从基类中担任的所有成员的可见性稳定(假如是protected担任,降一级,public酿成protected,private担任再降),那么对付一个从基类担任来的private成员来说,派生类是无法会见的(很疑惑,是么?),固然派生类含有这个变量,可是这是一种间接的拥有干系,在派生类中,含有一个基类的子工具,派生类对基类成员的会见正是通过这个子工具举办的。在思想中,永远不要把担任来的成员看做是本身真正拥有的,固然利用this可以直接"看到"它们,在派生体系中private和public与其它环境没有任何区别,而protected在体系的内部就和public完全等同。
由于派生类拥有基类的成员,所以我们可以通过派生而简朴的"重用"我们已有的代码,从而大大淘汰反复劳动。
关于担任的别的一个重要的特征就是虚函数,虚函数是形成类的多态的基本,在上面的类中:
void knight::hit(actor *Actor)
好比下面的伪码:
actor* pA;
knight* pKnight = new knight;
pA = pKnight;
pA->hit(…);
这里pA是一个基类的指针,而它指向的是一个派生类knight的指针,挪用它应该是怎么样的环境呢?谜底是hit会挪用knight的要领,而不是基类的,因为它是一个虚函数,虚函数的特点是它永远忠实与实际的工具(前提是正确的利用),通过这种多态的特性,我们可以利用基类来对派生类举办正确的操纵,这就办理了一个上面的问题:利用一种群体范例来描写它们,所以我们可以这样来遍历数据,而不需要体贴内里毕竟是一些什么样的数据:
vector troop1;
vector troop2;
vector::iterator it_tp1 = troop1.begin();
vector::iterator it_tp2 = troop2.begin();
while( (it_tp1!=troop1.end()) && (it_tp2!=troop2.end()) )
{
while(((*it_tp1)->isAlive())&&(it_tp2!=troop2.end()))
{
(*it_tp1)->hit(*it_tp2);
if(!(*it_tp2)->isAlive())
it_tp2++;
}
it_tp1++;
}
面向工具思想中的担任长短常重要的观念,正确的运用它,会为软件的开拓进程带来许多便利,同时,COM的焦点技能是利用了多重担任来实现的。同时,担任也是面向工具的思想中较难领略的一个观念,这片文章我只能大抵的报告其运用,而真正的交融意会还需要长时间的进修和实践。
下面我们给出上面的例子的完整代码,这段代码完成了随机成立两个步队,并举办战斗,直到有一个步队全军淹没,最后输出功效。
本文配套源码