07功能之装备系统的实现
前提:
我们知道,C++功能系统的实现思想是:继承和组合,优先使用组合。原因是组合可以更多的节省代码,而继承代码多,并且继承下来成员很混乱。 但是作者这里就使用了继承,哈哈,组合的话大家自己去试着实现。
1 整个装备系统的框架逻辑
1)英雄与装备的关系:装备继承后仍是英雄。
2)对业务层的代码分析可得出,每一个装备对象都是由上一个对象(装备类或者英雄类)的数据赋值得到,然后上一对象被释放,剩下的对象成为新英雄对象。
2 思考如何实现抽象类,即上面的英雄基类和装备基类
1)继承时,抽象类有数据的,是在抽象类提供构造自己初始化,然后给子类调用;还是子类自己在自己的构造初始化抽象类的数据呢?
答:虽然抽象类提供构造给子类调用很方便,但是有时会比较混乱。所以这里建议是子类统一自己初始化,方便整个逻辑清晰。并且一般抽象类都是提供纯虚函数的方法,其他都不需要处理。但抽象类若是继承于另一抽象类的,构造一般会实现,例如抽象装备类。
注:我这里写抽象英雄类时是实现了构造,做反例。想要工整的代码去查看我以前写的设计模式3的装饰模式。
class AbstractHero {
public:
//AbstractHero() { mHp = 0; } 不建议实现构造,由子类提供初始化
//私有子类不能访问 必须是保护或者公有
public:
int mHp; //英雄血量
};
2)继承时,英雄的显示状态函数是声明在装备基类还是英雄基类呢?
答:必须写在英雄基类才能给所有英雄显示状态。若写在装备基类,当AbstractHero *hero= new HeroA;时,指针hero是无法调用到装备类的方法。
3)由于是继承,我们为了析构基类的对象时,也能够析构子类的析构函数,抽象类一般也会声明析构函数为虚函数。
4)代码讲述抽象类,方便更好的理解抽象类的实现。
class AbstractHero {
public:
//这个一般不实现,因为抽象类不可实例化,虽然有数据时,方便子类统一调用赋值,但混乱
//以后有数据的也不要在抽象类定义构造赋值,统一子类中赋值,方便逻辑清晰
AbstractHero() { mHp = 0; } //不建议的写法,这里做反例
//这个函数抽象类基本都要实现
virtual ~AbstractHero(){}
//英雄状态显示,所以英雄必须都能显示,不能写在装备抽象类,否则英雄无法显示
virtual void ShowHeroStatus() = 0;
public:
int mHp; //英雄血量
};
3 如何实现装备具体类
1)实际上,实现装备具体类是不难的,就是在原本英雄的情况下改变他的属性值赋给装备的数据即可。一般有构造改变、Set方法改变。例如:
这里需要注意的是:必须是上一英雄数据+改变定值赋给本类的数据,即装备类。
this->mHp代表装备类的数据,而this->mHero->mHp是上一英雄的数据。
Wujin(AbstractHero *hero) :AbstractEquipment(hero) {
// 属性值的改变,即当前数据 = 上一英雄数据 + 装备值
this->mHp = this->mHero->mHp + 40;
}
// 通过方法设置英雄
virtual void SetHero(AbstractHero *hero) {
this->mHero = hero;
this->mHp = this->mHero->mHp + 40;
}
4 下面给出完整代码:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class AbstractHero {
public:
//这个一般不实现,因为抽象类不可实例化,虽然有数据时,方便子类统一调用赋值,但混乱
//以后有数据的也不要在抽象类定义构造赋值,统一子类中赋值,方便逻辑清晰
AbstractHero() { mHp = 0; } //不建议的写法,这里做反例
//这个函数抽象类基本都要实现
virtual ~AbstractHero(){}
//英雄状态显示,所以英雄必须都能显示,不能写在装备抽象类,否则英雄无法显示
virtual void ShowHeroStatus() = 0;
//私有子类不能访问 必须是保护
public:
int mHp; //英雄血量
};
class HeroA :public AbstractHero {
public:
HeroA():AbstractHero() {
//this->mHp = 0;//基类不实现就这样写
}
virtual void ShowHeroStatus() {
cout << "英雄A血量=" << mHp << endl;
}
};
class HeroB :public AbstractHero {
public:
HeroB():AbstractHero(){}
virtual void ShowHeroStatus() {
cout << "英雄B血量=" << mHp << endl;
}
};
//装备抽象类(不允许实例化为对象) 继承仍是英雄
class AbstractEquipment :public AbstractHero {
public:
//注:构造函数不能时虚函数
AbstractEquipment():AbstractHero() { this->mHero = NULL; }
virtual ~AbstractEquipment(){}
AbstractEquipment(AbstractHero *hero):AbstractHero() {
this->mHero = hero;
}
//外部方法设置英雄
virtual void SetHero(AbstractHero *hero) = 0; //这种写法子类必须实现,空阔号则不用,看个人
//展示穿上装备后的英雄数据
virtual void ShowHeroStatus() {};
protected:
AbstractHero *mHero; //写在抽象类方便子类统一调用该抽象类的构造
};
//无尽装备 (装备类重点:最重要的两个函数就是设置英雄。相当于给英雄穿上装备。)
class Wujin :public AbstractEquipment {
public:
Wujin():AbstractEquipment() {}
// 通过构造设置英雄
Wujin(AbstractHero *hero) :AbstractEquipment(hero) {
// 属性值的改变,即当前数据 = 上一英雄数据 + 装备值
this->mHp = this->mHero->mHp + 40;
}
// 通过方法设置英雄
virtual void SetHero(AbstractHero *hero) {
this->mHero = hero;
this->mHp = this->mHero->mHp + 40;
}
virtual void ShowHeroStatus() {
cout << "穿上无尽后的血量" << this->mHp << endl;
delete[] this->mHero;
this->mHero = NULL;
}
~Wujin() {
if (this->mHero != NULL) {
//delete[] this->mHero; //写析构的话必须delete[] Wujin才能释放原英雄,所以暂时写在Show
//this->mHero = NULL;
}
}
private:
//Wujin *mWu; //不需要
};
//狂徒装备
class Kuangtu :public AbstractEquipment {
public:
Kuangtu() :AbstractEquipment() {}
// 通过构造设置英雄
Kuangtu(AbstractHero *hero) :AbstractEquipment(hero) {
// 穿上狂徒后的英雄属性值改变(可以单独写一个方法) 注:是每次用上一个英雄赋值给当前装备成为新英雄
this->mHp = this->mHero->mHp + 20;
}
// 通过方法设置英雄
virtual void SetHero(AbstractHero *hero) {
this->mHero = hero;
this->mHp = this->mHero->mHp + 20;
}
// 必须在抽象英雄类声明
virtual void ShowHeroStatus() {
cout << "穿上狂徒后的血量" << this->mHp << endl;
delete[] this->mHero;
this->mHero = NULL;
}
~Kuangtu() {}
private:
};
void test01() {
// 注:是每次用上一个英雄赋值给当前装备成为新英雄,上一英雄被释放
AbstractHero *heroA = new HeroA;
//英雄没有装备
heroA->ShowHeroStatus();
cout << "===========" << endl;
//穿上无尽
heroA = new Wujin(heroA);
heroA->ShowHeroStatus();
//delete[] heroA->mHero;释放原英雄时,由于protected只能在类内释放
cout << "===========" << endl;
//穿上狂徒
heroA = new Kuangtu(heroA);
heroA->ShowHeroStatus();
//最后释放调用该英雄
delete[] heroA;
}
int main() {
test01();
return 0;
}
5 总结
1)抽象类的实现:
第一层的抽象类一般只提供纯虚方法,不实现构造;第二层即抽象类继承抽象类时,构造一般都会提供。其实这些都是看经验或者个人的,没有规定死的;
2)装备类的实现:
装备具体类其实就是提供改变属性值的方法;
3)内存关系:
每次用原英雄给新装备赋值,并释放掉原英雄,这样新装备就变成了新的英雄。