我正在创建一个基于文本的RPG,其中我有一个抽象的Item
类。在这个Item
课程中,我有课程Weapon
,Potion
,Key
和Armor
。主要角色类Protagonist
使用这些项目并具有函数doItemEffect(Item*)
。如何以一种普遍引用所有项目的方式实现doItemEffect(Item*)
?为了更好地表达我的问题,如果我不清楚,这是一个使用非常难看的解决方案的例子。
class Protagonist
{
public:
void doItemEffect(Item* it)
{
switch(it->getType()) //<-- The type is an enum class
{
case ItemType::WEAPON:
attackOpponent(it.getAttackPower()); //If it is a weapon it would have this function
break;
case ItemType::POTION:
heal(it.getHealPower()); //If it is a weapon it would have this function
break;
case ItemType::KEY:
//..Code..
break;
case ItemType::ARMOR:
//More Code...
break;
}
};
两个类Potion
和Weapon
的示例(类的类型是存储在Item
中的私有变量,带有mutator方法setType()
):
class Potion : public Item
{
int healPower;
public:
Potion(std::string name, std::string description) : Item(name, description)
{
setType(ItemType::POTION);
}
//Code
};
武器:
class Weapon : public Item
{
int attackPower;
public:
Weapon(std::string name, std::string description) : Item(name, description)
{
setType(ItemType::WEAPON);
}
//Code
};
如您所见,此代码依赖于Protagonist
类中的类代码和开关。因此,这似乎不是面向对象或多态的。因此,有没有一种方法可以得到一个Item类型的子类,而不必使用类代码?或者还有其他解决方案吗?上面这个片段的另一个问题是每当我引用其类之外的项时,我必须对每种类型的项使用相同的switch语句。
答案 0 :(得分:6)
在use()
课程中创建虚拟函数Item
。从派生类中重写此函数以触发各种操作(攻击,修复等),以便所有子类项都有一个抽象接口来使用/应用它们。
答案 1 :(得分:0)
您可以使用RTTI(例如dynamic_cast<>()
)替代专用类型字段:
class Protagonist
{
public:
void doItemEffect(Item* it)
{
Potion *potion = dynamic_cast<Potion *>(item);
Weapon *weapon = dynamic_cast<Weapon *>(item);
if (potion != nullptr) {
heal(potion->getHealPower());
}
else if (weapon != nullptr) {
attackOpponent(weapon->getAttackPower());
}
或通过在抽象effect()
类中添加虚拟Item
类成员函数来使用多态:
class Item {
// ...
public:
virtual void effect(Protagonist *) = 0;
// ...
};
并在派生类中重写它:
class Potion : public Item
{
// ...
public:
void effect(Protagonist *) override;
};
,其缺点是您的Potion类需要知道它可以被Protagonist
使用。为了解决这个问题,经常使用double dispatch。问题是C ++不支持双重调度作为语言功能。它可以使用访客模式进行模拟:
class Weapon;
class Potion;
class DispatchReceiver {
public:
virtual void effect(Weapon *) = 0;
virtual void effect(Potion *) = 0;
};
class Item {
// ...
public:
virtual void effect(DispatchReceiver *) = 0;
// ...
};
class Potion : public Item {
// ...
virtual void effect(DispatchReceiver *dr) override
{
dr->effect(this);
}
// ...
};
class Weapon : public Item {
// ...
public:
virtual void effect(DispatchReceiver *dr) override
{
dr->effect(this);
}
// ...
};
class Protagonist : public DispatchReceiver {
// ...
public:
void effect(Weapon *weapon) override
{
attackOpponent(weapon->getAttackPower());
}
void effect(Potion *potion) override
{
heal(potion->getHealPower());
}
void doItemEffect(Item* it)
{
it->effect(this);
}
};
答案 2 :(得分:0)
有一个项目类型列表
template<class...Types>
struct type_list_t{};
using Items=type_list_t<Potion, Weapon, etc>;
这取代了你的枚举。您可以编写get索引类型,并从(编译时)索引中获取类型。您甚至可以编写我称之为魔术开关的内容,通过延续传递样式将运行时(有界)索引映射到编译时类型。
接下来向Item
添加一个访问方法。它接受类型列表的索引,然后静态将其转换为子类型,然后调用带有转换结果的传入回调。
写函数重载,如下所示:
void use_item( Protagonist*, Potion* );
void use_item( Protagonist*, Weapon* );
然后使用Protagonist中的访问发送给它。
现在,您可以使用预先编写的variant
来简化此操作。
template<class Base, class...Ts>
struct poly_variant:boost::variant<Ts...>{
using boost::variant<Ts...>::variant;
Base& base();
Base const& base() const;
};
现在您可以访问Protagonist。如果要使存储成为指针(或智能指针),可以使用此变体。
通过申请访问者撰写base()
。