混入 ( M i x i n s ) (Mixins) (Mixins)这个概念,是一种编程手法,用于描述类与类之间的一种关系,这种关系比较类似于多重继承,看起来更像颠倒过来的继承(基类继承派生类)。
混入的实现手段是把传入的模板参数当做该类模板的父亲。
一、常规范例
如果我们设计一些类,比如我们这里以一个大型网络游戏为例,游戏中每个玩家代表一个角色,多个角色可以组成一个家族,家族与家族之间可以进行战斗。
因此,我们需要设计一个 r o l e role role类,代表玩家,具有相应的属性,代码如下:
//角色类
class role {
private:
double m_attack;
double m_defence;
double m_life;
public:
role()
:m_attack(0.0), m_defence(0.0), m_life(100) {}
role(double att, double def, double life)
:m_attack(att), m_defence(def), m_life(life) {};
};
然后,我们需要设计一个家族类,多个角色可以组成一个家族:
//家族类,若干个角色构成一个家族
template<typename T>
class family {
public:
std::vector<T>m_members;
};
这里省略一些细节,只需要知道大体结构即可。
当然,一个游戏肯定少不了 N P C NPC NPC,那么我们再来引入一个 N P C NPC NPC类,为了方便,我们单独给 N P C NPC NPC的属性创建一个类:
//NPC属性
struct npcattr {
int m_sort; //npc类型
std::string m_lang; //npc的旁白
};
因为 N P C NPC NPC也具有角色的所有属性,因此让 N P C NPC NPC继承自 r o l e role role类。
//role的派生类,role_npc
class role_npc :public role {
public:
npcattr m_strucattr; //npc属性
role_npc() :role(), m_strucattr{ 0,"" } {}
role_npc(double att, double def, double life, int sort, std::string lang)
:role(att, def, life), m_strucattr{ sort,lang } {}
};
紧接着,引入玩家类 r o l e _ p l a y e r role\_player role_player,同样继承与 r o l e role role类,然后我们也为其单独写一个玩家属性类:代码如下:
//玩家属性
struct playerattr {
int m_strength;
int m_agile;
int m_constitution;
};
//玩家角色类
class role_player :public role {
public:
playerattr m_strucattr; //玩家属性
role_player() : role(), m_strucattr{ 0,0,0 } {}
role_player(double att, double def, double life, int sth, int agi, int cons)
: role(att, def, life), m_strucattr{ sth,agi,cons } {}
};
以上就是全部的代码了 ,现在我们看看使用混入这个技术如何实现。
二、使用混入的技术
使用混入可以减少代码量,不需要每次引新的类通过繁琐的继承来实现,而是通过泛型模板来实现。
具体如下:
同样的,
N
P
C
NPC
NPC属性和玩家属性是必要的:
//NPC属性
struct npcattr {
int m_sort; //npc类型
std::string m_lang; //npc的旁白
};
//玩家属性
struct playerattr {
int m_strength; //力量
int m_agile; //敏捷
int m_constitution;//体质
};
注意看下面的
r
o
l
e
role
role类,我们使用模板,继承可变参基类包,这样后续就可以直接多继承了。
对于基类参数包的展开,这个博客有介绍:基类参数包的展开
具体代码如下:
template<typename...T>
class role :public T...{ //传入的模板参数作为基类
public:
double m_attack;
double m_defence;
double m_life;
role() :T()..., m_attack(0.0), m_defence(0.0), m_life(100.0){};
role(double att, double def, double life)
:T()..., m_attack(att), m_defence(def), m_life(life){};
};
这里实际上是一个多继承,在实例化模板的时候会先实例化出基类,然后最后再实例化出 r o l e role role类,因此这个 r o l e role role类会具有所有基类的属性。
那么如何定义一个玩家类或者
N
P
C
NPC
NPC类呢?答案呼之欲出:继承玩家属性类,继承
N
P
C
NPC
NPC属性类!
是的,具体代码如下:
我们使用 u s i n g using using来简化类型名,这样代码会更简洁一点。
using role_npc = role<npcattr>; //类似与多继承
void Test1() {
role_npc mynpc;
mynpc.m_attack = 15;
mynpc.m_defence = 10;
mynpc.m_life = 120;
mynpc.m_sort = 1;
mynpc.m_lang = "Are you OK ?";
}
//同样可以构造出这样的玩家类
using role_player = role<playerattr>;
void Test2() {
role_player mynpc;
mynpc.m_attack = 15;
mynpc.m_defence = 10;
mynpc.m_life = 120;
mynpc.m_strength = 100;
mynpc.m_agile = 130;
mynpc.m_constitution = 150;
}
当然,由于是继承的可变基类参数包,我们可以定义更复杂的类,比如具有玩家和 N P C NPC NPC共同属性的类,如下:
using role_mixnpc = role<npcattr, playerattr>;
void Test3() {
role_mixnpc mynpc;
mynpc.m_attack = 15;
mynpc.m_defence = 10;
mynpc.m_life = 120;
mynpc.m_sort = 1;
mynpc.m_lang = "Are you OK ?";
mynpc.m_strength = 100;
mynpc.m_agile = 130;
mynpc.m_constitution = 150;
}
这样看起来像是随意的排列组合,因此混入这个词看上去十分恰当。
最后,我们完善一下家族类。
由于
r
o
l
e
role
role类是一个可变参模板,因此家族类也应该是一个可变参模板,如下:
//家族类
template<typename...T>
class family {
public:
std::vector<role<T...>>m_member;
//其他信息...
};
对于玩家家族和 N P C NPC NPC家族也同样是通过混入的方式来实现:
using family_npc = family<npcattr>; //npc家族
void Test4() {
role_npc mynpc;
mynpc.m_attack = 15;
mynpc.m_defence = 10;
mynpc.m_life = 120;
mynpc.m_sort = 1;
mynpc.m_lang = "Are you OK ?";
family_npc myfamily;
myfamily.m_member.push_back(mynpc);
}
//一样的,玩家家族
using family_player = family<playerattr>; //玩家家族
这样的写法大大简化了代码量,并且日后再增加新内容的时候可以直接通过已有内容进行排列即可,无需再构建新的类。