【问题标题】:Common interface for all derived classes所有派生类的通用接口
【发布时间】:2014-06-08 09:05:04
【问题描述】:

我有基类 Item ,它存储一些数据并授予访问者对其的访问权限,例如:

class Item{
  (...)
public:
  int get_value();
  double get_weight();
  ItemMaterial get_material();
  (...)
}

然后我得到了像 Weapon、Armor 这样的派生类,它们添加了一些额外的数据:

class Weapon : public Item {
(...)
public:
  int get_dmg();
  (...)
}

我将这些项目存储在某个容器中:

std::vector<Item*> inventory;

接口的问题来了——如何访问派生类数据?我在想,得到了 3 个想法:

1.单独的接口

每个派生类添加其数据,如上图所示,然后使用dynamic_cast:

Item *item = new Weapon;
int dmg = dynamic_cast<Weapon*>(item)->get_dmg();

2。通用接口类

创建一个包含所有访问器的接口类:

ItemInterface{
public:
  virtual int get_value() = 0;     //Item interface
  virtual double get_weight() = 0;
  (..)
  virtual int get_dmg() = 0;       //Weapon interface
  (...)
}

然后是这样的:

Item : public ItemInterface{ (...) }

Weapon : public Item { (...) }

最后我们可以访问数据了:

Item *item = new Weapon;
int dmg = item->get_dmg();

3.结合模板和枚举

这个想法可能有点奇怪 :-) 但是:

使用所有项目数据实现枚举:

enum class ItemData{
  Value,
  Weight,
  Material,  //Item data
  (...)
  Damage,    //Weapon data
  (...)
  Defense,   //armor data etc.
  (...)
  Null
}

在基类中一些模板函数是这样的:

template<typename T>
T get_data(ItemData data){
  switch(data){
    case ItemData::Value: return _value; break;
    case ItemData::Damage: return _dmg; break;
    (...)
  }
}

并访问以下数据:

Item *item = new Weapon;
ind dmg = item->get_data<int>(ItemData::Damage);

===

你觉得应该怎么做?如有任何建议,我将不胜感激!

问候。

【问题讨论】:

  • 如果您需要访问特定的派生类内容,为什么要使用指向基的指针?换句话说,给定一个任意的Item*,你怎么知道它适合转换为Weapon* 以获得武器专用的东西?
  • 没有一个好 - 如果你不能为你的数据建模,使用 dynamic_cast
  • 因为,例如,对象 Creature 包含库存,如示例所示,并且我想在此容器中保存所有基于 Item 的类。如果合适? dynamic_cast 给了我这个信息。在其他情况下,我认为我应该,例如,在提供此信息的基类中添加一些数据字段..
  • @Dieter Lücking:那么第一个解决方案?
  • 查看访问者模式以获得另一种选择。

标签: c++


【解决方案1】:

您的第二个和第三个选项显然不是要走的路 - 每当您添加新类型的项目时,您还必须更改基类或枚举 - 这绝对不是什么如果您的代码需要任何基本形式的可维护性,您愿意。

接口的问题来了——如何访问派生类数据

首先您必须考虑“您的代码将在哪里执行此操作”?大多数处理整个inventory 的代码应该只使用Item* 的内容,只使用Item 类中的函数。

如果您有专门处理Weapon 对象的代码,则创建Weapon 对象的位置(并插入inventory)也可以将它们添加到另一个变量,可能是形式为的武器列表一个

std::vector<Weapon*> weapons;

或类Warrior 的成员变量Weapon* 或类似的东西(但请注意,您现在将有两个指向相同对象的指针,因此您必须考虑所有权)。所以只处理武器的代码(例如Warrior的成员函数)不会访问inventory来获取Weapon对象,它总是会直接使用Weapon*

如果由于某些原因,您必须编写一些代码来处理库存中的所有武器,然后编写一个函数,使用 dynamic_cast 提取所有 Weapon 对象(或者更好:使其成为迭代器函数),并在需要访问所有武器时重用此函数。因此,您不会因为动态转换而将代码弄得乱七八糟,而是将其保存在一个地方。

编辑:另一种选择(避免动态转换)是使用访问者模式,请参阅this post。但我不太喜欢那篇文章的答案,在呈现的形式中,它将暗示一个循环依赖“基础 - > 访客 - > 派生 - > 基础”,恕我直言,这是一个糟糕的设计。

【讨论】:

    【解决方案2】:
    ValueType Weapon::getProprtyValue( PropertyType id ) {
       switch( id ) {
          case kWeaponProperty01: return m_weaponProperty01;
          ...
          default: return Item::getPropertyValue( id );
       }
    }
    

    您可以制作某种通用访问器方法,虽然它有一些限制,但它可能非常方便,尤其是在内容编辑器、序列化等情况下。

    【讨论】:

    • 这有点像 EAV 模型en.wikipedia.org/wiki/…,它完全避免了继承的需要,这使得这个问题毫无意义。这可以在某些特定场景中成为一种解决方案。但是我不质疑给定的 OP 继承树的正确性,这个问题对于所有类型的类模型都有效,其中给出了一个指向基类元素的向量。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-12
    • 1970-01-01
    • 2021-08-29
    • 2014-12-28
    • 2019-07-15
    • 2014-07-07
    相关资源
    最近更新 更多