Armor and Weapon classes for a game I have a 2 classes Armor and Weapon in my game, that inherit from the abstract class Gear:

public class Gear {
    public int price;
    // Other common attributes between armors and weapons
}

public class Weapon : Gear {
    public int damage = 10;
    public int weight = 10;
    public int range = 10;
}

public class Armor : Gear {
    // Defense against different kinds of attack
    public int blunt, slash, ranged;
}


Then I have a class GearButton which is nothing more than a button, with a piece of gear associated. I inherit from this class to create Buttons to Buy, Equip or Upgrade this items, for instance.

This is 2 examples of it:

BuyArmorButton.cs

public class BuyArmorButton : GearButton {

    // ...

    void BuyItem() {
        bool bought = GameSession.Buy((Armor) this.gear);
        if (bought) this.ShowMessage('Buy successful');
    }

    void Update() {
        bool can_buy = GameSession.CanBuy((Armor) this.gear);
        this.EnableButton(can_buy);
    }

    // ...
}


BuyWeaponButton.cs

public class BuyWeaponButton : GearButton {

    // ...

    void BuyItem() {
        bool bought = GameSession.Buy((Weapon) this.gear);
        if (bought) this.ShowMessage('Buy successful');
    }

    void Update() {
        bool can_buy = GameSession.CanBuy((Weapon) this.gear);
        this.EnableButton(can_buy);
    }

    // ...
}


I have added the methods from GameSession, which probably might also need to be tweaked, for this to work.

public static bool Buy(Armor armor) {
    if (!CanBuy(armor)) return false;

    data.money -= armor.price;
    data.armorsPurchased.Add(armor);
    return true;
}

public static bool Buy(Weapon weapon) {
    if (!CanBuy(weapon)) return false;

    data.money -= weapon.price;
    data.weaponsPurchased.Add(weapon);
    return true;
}


public static bool CanBuy(Armor armor) {
    if (data.armorsPurchased.Contains(armor))
        return false;

    if (armor.price &gt; data.money)
        return false;

    return true;
}

public static bool CanBuy(Weapon weapon) {
    if (data.weaponsPurchased.Contains(weapon))
        return false;

    if (weapon.price &gt; data.money)
        return false;

    return true;
}


It kinda feels like a lot of the code duplication, and I thought it could be merged into a BuyGearButton class, but I am not sure how to go about it, because of the up-casting of the Gear classes.

I have pasted the whole files here:


Gear.cs
Weapon.cs
Armor.cs
GameSession.cs
BuyArmorButton.cs
BuyWeaponButton.cs
Once various responsibilites (functionality) is separated into appropriate classes, inheritance and using subclasses is not a problem.

Rethink your core classes. Don't make client code do the work - that's a guarantee of duplication. A good class hides state and exposes functionality. Even basic functionality like overriding Equals pays off. "Do not make client code do the work for that class". Repeat this a million times. Oh, I expect you will also avoid tons of debugging.

Some methods are overloaded. The point is when you see a need for something different put that in the proper class. Don't be lazy and write it in the calling class because it's easy and quick.

SPOILER: gear and gear collections are just data structures. Abstract things. A Warrior has his personal stuff (a gear collection) and an Armory (a really big gear collection) is where he buys his stuff. 



Gear enum

Looks like you care only if some gear is a weapon, armor, etc. not a specific object.

public enum GearType { undefined, Weapon, Armor }




Make Gear abstract

And give it all the common gear behavior.

public abstract class Gear {
    protected decimal price { get; set; }
    public GearType Type { get; protected set; } // set only in a constructor

    public virtual bool IsSameKind( Gear otherGear ) {
        return this.Equals( otherGear );
    }

    // I thought of this as I was working on the collection class below
    public virtual bool IsSameKind( GearType thisKind ) { return this.Type == thisKind; }

    // any armor is armor. any weapon is weapon....
    /// &lt;summary&gt;
    /// Equates on Type property. 
    ///&lt;/summary&gt;
    public override Equals( Object otherGear ) {
       if( otherGear == null ) return false;

       otherGear = otherGear as Gear;
       if( otherGear == null ) return false;

       return this.Type == otherGear.Type;
    }
}




Use Constructors

To guarantee valid, complete, correct objects.

public class Armor : Gear {
   protected int blunt { get; set; }
   protected int slash { get; set; }
   protected int range { get; set; }

   public Armor ( decimal price, int blunt, int slash, int range ) {
      this.Type = GearType.Armor;
      // set other properties here
   }
}




Custom Collections

Solves your upcast issue.

Also higher level code will make more sense, is cleaner, clearer, and simpler. 

publc class GearCollection {
   protected List&lt;Gear&gt; Armory { get; set; }

   public GearCollection() { Armory = new List&lt;Gear&gt;(); }

   public void Add ( Gear thing ) {
       if ( thing == null ) return;
       Armory.Add( thing );
   }

   // Passing in a Gear reference. The OBJECT itself will be
   // a "Weapon", "Armor", etc.
   public bool Contains( Gear otherThing ) {
       if ( otherThing == null ) return false;
       if ( Armory.Contains( otherThing ) ) return true;  // uses equals override.
       return false;
   }

   public bool Contains( GearType thisGear ){
      bool gotIt = false;

      foreach( var item in Armory ) {
         if( item.IsSameKind( thisGear ) {
            gotIt = true;
            break;
         }
      }            
      return gotIt;
  }

   public GearCollection GetAll( GearType thisKind ){
      GearCollection sameGear = new GearCollection();

      foreach( var item in Armory ) {
         if( item.IsKind ( thisKind ) {
            sameGear.Add( item );
         }
      }
      return sameGear;
   }

   // this method makes THIS collection object mutable.
   // a discussion for another time.
   public GearCollection GetAll( Gear likeThis ){
      GearCollection sameGear = new GearCollection();

      foreach( var item in Armory ) {
         if( item.Equals( likeThis ) ) sameGear.Add( item );
      }
      return sameGear;
   }
}




Separation of concerns

Why would a button have its own gear? That's a code smell to me. I'd expect event handlers to call methods on domain objects like Warrior or AmmoDump or WeaponCache. Those are things I expect to have Gear, not the UI controls.

public class Warrior {
    protected GearCollection MyGear { get; set; }
    public Warrior() { MyGear = new GearCollection(); }
    public Warrior( GearCollection standardIssue ) {
       MyGear = standardIssue?? new GearCollection();
    }
}

// The Armory is where you buy your gear. A gear collection is just
// a data structure =&gt; separation of concerns.
public class Armory {
   protected GearCollection Inventory { get; set; }

   // optional parameter so I don't have to write 
   // a contructor overload.
   public Armory( GearCollection newInventory = null ){
      Inventory = newInventory ?? new GearCollection();
   }

  // renamed "CanBuy". 
   public bool IsInStock( GearType gearKind ) {
       return Inventory.Contains( gearKind );
   }

   public bool IsInStock( Gear likeThis ) {
      return Inventory.Contains( likeThis ) {
   }

   public Gear Buy( GearType something, decimal cashOnly ) {
      if( !IsInStock( something ) )  return null;

      Gear newBling = Inventory.Remove( something );  // didn't write that method yet.
      if( cashOnly &lt; newBling.Price ) return null;

      return newBling;
   }

   public void AddInventory ( GearCollection newShipment ) {
      if( newShipment == null ) return;
      Inventory.AddRange( newShipment );
   }   
}




P.S. Now that we defined the Armory class, I'd go back and rename the GearCollection.Armory property. Make it more general, more abstract sounding because it is. The collection is a data structure. We want names appropriate for the class they are in.

The methods defined in Armory class is what brings the armory concept to life - not it's properites.



Edit


  could you write an answer with your suggestion of "type checking". Id love to see how you would do it.


It depends on how specific you want to be. The keyword is:


  An is expression evaluates to true if the provided expression is non-null, and the provided object can be cast to the provided type without causing an exception to be thrown.


If everything derives from Gear, that won't help too much looking into a GearCollection.

Keyword typeof returns a System.Type that you can compare to the typeof of other objects in a GearCollection. This is the specific type of your object.

Type Checking: typeof, GetType, or is?

end Edit