views:

52

answers:

1

In my game I have a base class of Loot which has methods universal to anything which can be picked up by the player and stored in his inventory. This would include potions, equipment, ammo, etc. I can equip arrows, but not potions. So Arrow would be a subclass of Ammo, which would ultimately derive from Loot. I can drink a potion but not an Arrow. So Potion would subclass Loot but implement IConsumeable. And so on.

Loot objects have a Quantity property (10 arrows; 2 potions). In my Loot class I have a method called Split which allows the player to take a "stack" of items (like arrows) and split it into two separate stacks. So it decreases the Arrow instance's Quantity by a certain amount, then returns a new Arrow instance with a Quantity value = to that which was taken from the original instance.

My idea was that I'd write the method in Loot since any Loot can be stacked so long as its int StackLimit property is greater than 1. After decrementing the calling Loot by the quantity specified, I'd return a new object of the same type. The problem is, I don't know what type of Loot subclass that object will be.

public abstract class Loot
{
    public int Quantity { get; set; }
    public Loot Split(int quantityToTake)
    {
        Loot clone = (Loot)this.MemberwiseClone();
        //RestrictNumberToRange(int min, int max, int value) is a delegate which clamps a value to min,max
        this.Quantity -= Utility.RestrictNumberToRange<int>(1, this._quantity - 1, quantityToTake);
        clone.Quantity = quantityToTake;
        return clone;
    }
}

Is this a really poor way to go about this? I thought about Reflection, but I hear mixed opinions on whether or not to use it in a case like this.

Is there no way to define the method to deal with this.FurthestSubClass?

I know my subclasses may have different constructors, so it's probably not feasible to try and return 'new this.FurthestSubclass()' because I can't know how to construct it. But I'd like to be able to deal with its Loot methods, so I'm using Loot for the return type.

+1  A: 

I think this is a good case for generics. Try rewriting your split method in Loot like this:

public TLoot Split<TLoot>(int quantityToTake) where TLoot : Loot
{
    TLoot clone = (TLoot)this.MemberwiseClone();
    ...
    return clone;
}

That ought to take care of typing issues.

EDIT TO ADD: The constructor issue is a bit more interesting, but you may find parameterless constructors useful, in combination with object initalizers. If you modify the constraint as follows:

public TLoot Split<TLoot>(int quantityToTake) where TLoot : Loot, new(TLoot)
{
    // stuff
    TLoot newLoot = new TLoot();
    ...
    return newLoot;
}

The "new(T)" constraint allows you to create new objects based on the generic type.

FURTHER EDIT: I should give an example of object initializer in this context:

TLoot newLoot = new TLoot { Quantity = quantityToTake };

This assumes that Loot has a public property called Quantity. Object initializers can set values for any public property that has a public set{};

Cylon Cat
You know, I just had something explained to me recently along these lines and I *thought* I got it. Clearly, I've not yet achieved Zen there. I'll give this a shot, thanks. So, to be clear: if this instance is an Arrow, which derives from Ammo, which derives from Loot, the Split method will return an Arrow?
Superstringcheese
@Super, yes, that's correct. Generics are absolutely wonderful!
Cylon Cat
As I said, I've seen this before (in fact the delegate in my example uses it), but seeing it applied here really opened my eyes - thanks, man.
Superstringcheese