BaseClass method which returns an arbitrary subclass of BaseClass
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>开发者_如何学JAVA(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.
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{};
If I'm correct, the only small drawback of this, is that you are forced to explicitly mention what kind of TLoot
you are splitting.
Arrow arrow1;
/* some code initializing arrow1 */
Arrow arrow2 = arrow1.Split<Arrow>(10);
Obviously, arrow1
being an Arrow
, we couldn't have written anything else (like for example Arrow b = a.Split<Potion>(10);
which makes no sense), yet Arrow arrow2 = arrow1.Split(10);
unfortunately wouldn't compile, since the return type of a generic method cannot be infered if it has no parameter of the same type.
Here is the trick : make Split
an extension method on Loot
. :-)
Its prototype becomes public static TLoot Split<TLoot>(this TLoot item, int quantityToTake) where TLoot : Loot
and TLoot
now takes place among the parameter at position 1 (even if introduced by the special keyword this
: nevermind !). This first parameter disappears when calling, so that all occurs as if we had return type inference.
You can now write Arrow arrow2 = arrow1.Split(10);
. It is perfectly legal and the returned type is an authentic strongly typed Arrow
(you could even write var arrow2 = arrow1.Split(10);
and then examine arrow2
's type to be sure).
I confess it seems more cool and elegant than really usefull in this context. But I'm fond of fluent interface (method chaining), and there, it really becomes a huge improvement. Compare these 2 declarations :
/*Given an certain among of arrows found in the treasure chamber,
each player in the team receives a fraction of it according to his age
and experience, but always 10 arrows at least anyway.
Ok, ok ! I had to search before finding such an example vaguely accurate
in the context of arrows and potions ! lol*/
foreach(Player p in Team)
p.Arrows = Treasure.Arrows.Split<Arrow>(10).Split<Arrow>(p.Age).Split<Arrow>(p.Experience*2);
foreach(Player p in Team)
p.Arrows = Treasure.Arrows.Split(10).Split(p.Age).Split(p.Experience*2);
Ok, here again the benefit remains poor (Treasure.Arrows.Split<Arrow>(10+p.Age+p.Experience*2)
works as well and is shorter !) but there really are situations where making the return type implicit hugely improves the readibility.
精彩评论