Reputation: 887
I have base class Item which store some data and grant access to it by accessors, like:
class Item{
(...)
public:
int get_value();
double get_weight();
ItemMaterial get_material();
(...)
}
Then I've got derived classes like Weapon, Armor which add some additional data:
class Weapon : public Item {
(...)
public:
int get_dmg();
(...)
}
I store these Items in some container:
std::vector<Item*> inventory;
And here comes the problem with interface - how to get access to derived class data? I was thinking, and got 3 ideas:
1. Separate interfaces
Each derived class adds its data, like it is shown above, and then use dynamic_cast:
Item *item = new Weapon;
int dmg = dynamic_cast<Weapon*>(item)->get_dmg();
2. Common interface class
Make an interface class with all accessors:
ItemInterface{
public:
virtual int get_value() = 0; //Item interface
virtual double get_weight() = 0;
(..)
virtual int get_dmg() = 0; //Weapon interface
(...)
}
And then something like this:
Item : public ItemInterface{ (...) }
and
Weapon : public Item { (...) }
and finally we can access the data:
Item *item = new Weapon;
int dmg = item->get_dmg();
3. Combination with templates and enums
This idea is maybe a little weird :-) but:
implement enum with all item data:
enum class ItemData{
Value,
Weight,
Material, //Item data
(...)
Damage, //Weapon data
(...)
Defense, //armor data etc.
(...)
Null
}
and in base class some template function like this:
template<typename T>
T get_data(ItemData data){
switch(data){
case ItemData::Value: return _value; break;
case ItemData::Damage: return _dmg; break;
(...)
}
}
and access data like:
Item *item = new Weapon;
ind dmg = item->get_data<int>(ItemData::Damage);
===
How do you think it should be done? I will be grateful for any advices!
Regards.
Upvotes: 3
Views: 779
Reputation: 138
ValueType Weapon::getProprtyValue( PropertyType id ) {
switch( id ) {
case kWeaponProperty01: return m_weaponProperty01;
...
default: return Item::getPropertyValue( id );
}
}
You can make some kind of universal accessor method, though it have some limitations, it could be quite handy, especially in case of content editors, serialization etc.
Upvotes: 1
Reputation: 20044
Your second and third option is obviously not the way to go - whenever you add a new type of item, you will also have to change the base class or the enum - that is definitely not what you want to if you need any basic form of maintainability in your code.
And here comes the problem with interface - how to get access to derived class data
First you have to think of "where will your code do this"? Most of your code dealing with the whole inventory
should only use the content as Item*
, using only functions from the Item
class.
If you have code specificially dealing with Weapon
objects, the place where the Weapon
objects are created (and inserted into the inventory
), may also add them to another variable, maybe a weapons list in form of a
std::vector<Weapon*> weapons;
or to a member variable Weapon*
of a class Warrior
or something like that (but beware, you now will have two pointers to the same objects, so you have to think about ownership). So the code dealing only with weapons (for example, a member function of Warrior
) does not access the inventory
to get a Weapon
object, it will always use the Weapon*
directly.
If, for some reasons, you have to write some code which does something for all weapons from your inventory, then write a single function which extracts all Weapon
objects using the dynamic_cast
(or even better: make it an iterator function), and reuse this function whenever you need to get access to all weapons. So you don't clutter your code all over with dynamic casts, but keep this in just one place.
EDIT: another alternative (avoiding the dynmic cast) is using the visitor pattern, see this post. But I don't really like the answer of that post, in the presented form it will imply a cyclic dependency "Base -> Visitor -> Derived -> Base", which is IMHO a bad design.
Upvotes: 2