【问题标题】:How to refer to instances of a class universally without type codes?如何在没有类型代码的情况下普遍引用类的实例?
【发布时间】:2017-11-04 10:04:15
【问题描述】:

我正在制作一个基于文本的 RPG,其中我有一个抽象的 Item 类。从这个Item 类,我有WeaponPotionKeyArmor 类。主要角色类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;
     }
};

以及PotionWeapon 中的两个类的示例(该类的类型是存储在Item 中的私有变量,带有一个突变方法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 类型的子类,而不必使用类代码?或者有没有其他解决方案?上面这个 sn-p 的另一个问题是,每当我引用其类之外的项目时,我必须对每种类型的项目使用相同的 switch 语句。

【问题讨论】:

  • Item一个虚函数,比如virtual void performEffectOn(Protagonist* p) = 0;。然后让每个项目相应地实施。例如。 Weapon::performEffectOn(Protagonist* p) { p-&gt;attackOpponent(getAttackPower()); } 最后,Protagonist::doItemEffect(Item* it) 将简单地调用 it-&gt;performEffectOn(this);
  • 这正是多态性解决的问题。
  • @IgorTandetnik 有没有办法在Item 不知道Protagonist 的情况下做到这一点。我最初的解决方案实现了你所说的,但我想要一个能够解决我所指问题的解决方案。
  • 我不确定我是否理解这个“你所指的问题”是什么。 Item 知道 Protagonist 有什么问题?无论如何,您可能正在寻找Visitor pattern

标签: c++ oop c++11 polymorphism


【解决方案1】:

Item 类中创建一个虚函数 use()。从您的派生类中重写此函数以触发各种操作(攻击、治疗等),以便您的所有子类项目都有一个抽象接口来使用/应用它们。

【讨论】:

  • 这很好,但是有没有办法在 Item 不知道它的所有者的情况下做到这一点?
  • @ArnavBorborah 我不确定我是否理解您的问题。您能否尝试改写或举一个更具体的例子?在我看来,如果你想让你的 Item 改变你的 Protagonist 的状态,它必须“知道”它。
  • 你说得很好,所以我将其标记为正确,因为我真的不想使用访客设计模式,因为有一个更简单的解决方案。谢谢!
【解决方案2】:

您可以使用 RTTI(例如 dynamic_cast&lt;&gt;())作为专用类型字段的替代方案:

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());
        }

或者通过在抽象Item类中添加一个虚拟effect()类成员函数来使用多态性:

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);
    }
};

【讨论】:

  • 第一个选项是制作中的恐怖片。它可以工作,但它不如第二个选项,我什至不会列出它。
  • @user4581301 我同意使用某种开关类型通常是次优设计的标志。但话又说回来,这条规则也有例外。在这种情况下,它确实暗示了一个糟糕的设计,因为有可行的设计替代方案,但出于教学原因(类型字段 -> RTTI 开关 -> 简单多态性 -> 双分派),我将其包括在内。
  • 在我看来,您的两种方法都有效地手动实现了虚拟调度。为什么武器不简单地调用attack()Potion 调用heal()?此时您已经通过虚函数调用选择了类型。无需将其传递给您自己制作的另一个调度员。 Protagonist 知道它是否可以使用药水或武器。
  • @Galik Items 需要知道attack()heal() 应该是Protagonist 的类成员函数。不过可能还有其他DispatchReceivers,比如class Antagonist : public DispatchReceiver { ... };Items 不需要知道他们的任何细节,使用如图所示的双重调度。
【解决方案3】:

有一个项目类型列表

template<class...Types>
struct type_list_t{};

using Items=type_list_t<Potion, Weapon, etc>;

这将替换您的枚举。您可以编写获取类型的索引,并从(编译时)索引获取类型。您甚至可以编写我所说的魔术开关,通过延续传递样式将运行时(有界)索引映射到编译时类型。

接下来将访问方法添加到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;
};

现在您可以在主角中访问。如果您想让存储成为指针(或智能指针),可以使用这种变体。

通过申请访问者写base()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-20
    • 1970-01-01
    • 2021-01-07
    • 2018-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-14
    相关资源
    最近更新 更多