subclass properties accessed in generic method wit

2019-08-04 10:14发布

I want to make an inventory/equipment system to interface with my item system. My item system is an inherited class system based around shared properties.

I store types in enums and give the items an enum value.

My inheritance structure:

Item {
  Equippable {
    Armor {Chest, Head}
    Weapon {Melee, Ranged}
  }
}

I have each subType define an enum variable in its parent class, so Chest would define the armorTypes enum in an explicit base constructor call with the type.

e.g.

public enum armorTypes {Chest, Helmet}

public class Armor : Equippable
{
    armorTypes armorType;

    public Armor(armorTypes at) : base(equippableTypes.Armor) 
    {
        armorType = at;
    }
}

public class Chest : Armor 
{
    int defense = 10;

    public Chest() : base(armorTypes.Chest) {}
}

public class Helmet : Armor 
{
    int defense = 5;

    public Helmet() : base(armorTypes.Helmet) {}
}

In my Inventory class I have blank versions of these items.

Inventory{
  public Chest chestSlot;
  public Helmet helmetSlot;
  public Melee meleeSlot;
  public Ranged rangedSlot;
}

I want to compare the objects in the two slots based on their properties.

void Compare(Item item) 
{
  switch((item as Armor).armorType)
  {
      case armorTypes.Chest :
        Console.WriteLine((item as Chest).defense + " or " + chestSlot.defense);
        break;
      case armorTypes.Helmet :
        Console.WriteLine((item as Helmet).defense + " or " + helmetSlot.defense);
        break;
  }
}

However, when I cast it using the as keyword, somehow I lose my instance?

Also, as I need to access item.armorType I cannot encapsulate Armor, can I?

1条回答
ゆ 、 Hurt°
2楼-- · 2019-08-04 10:23

This is how I'd implement this. First of all, let the base class define basic comparison methods:

abstract class Item 
{
    // these methods define base comparison operations;
    // by default, items are not comparable;
    public virtual bool CanCompareWith(Item item)
    {
        return false;
    }

    // since we don't know all possible item properties,
    // this method hasn't implementation
    public abstract void CompareWith(Item item);
}

Next, we must define some default equipment. Armor:

abstract class Equippable : Item {}    

abstract class Armor : Equippable
{
    // every armor has a Defence property
    public abstract int Defence { get; }

    // the base comparison logic of two armors
    public override void CompareWith(Item item)
    {
        Console.WriteLine("{0} or {1}", ((Armor)item).Defence, this.Defence);
    }
}

class Chest : Armor
{
    // if you want for chests to be comared with chests only:
    public override bool CanCompareWith(Item item)
    {
        return item is Chest;
    }

    public override int Defence
    {
        get { return 10; }
    }
}

class Helmet : Armor
{
    public override bool CanCompareWith(Item item)
    {
        return item is Helmet;
    }

    public override int Defence
    {
        get { return 5; }
    }
}

Weapon:

abstract class Weapon : Equippable
{
    // every weapon has Attack property
    public abstract int Attack { get; }

    // the base comparison logc of two weapons
    public override void CompareWith(Item item)
    {
        Console.WriteLine("{0} or {1}", ((Weapon)item).Attack, this.Attack);
    }
}

class Melee : Weapon
{
    public override bool CanCompareWith(Item item)
    {
        return item is Melee;
    }

    public override int Attack
    {
        get { return 20; }
    }
}

class Ranged : Weapon
{
    public override bool CanCompareWith(Item item)
    {
        return item is Ranged;
    }

    public override int Attack
    {
        get { return 25; }
    }
}

Note, that there's no any enums here. This is because the type itself (e.g., Chest, Helmet, etc.) defines, where concrete item belongs to. Enum is just superfluous here.

Here's the inventory:

class Inventory
{
    // this is inventory storage;
    // collection allows the storage to be extended later, or be displayed at once
    // (e.g., some ShowAllInventory method)
    private readonly List<Item> inventoryItems;

    public Inventory()
    {
        inventoryItems = new List<Item>
        {
            // some predefined inventory slots with default inventory items
            new Chest(),
            new Helmet(),
            new Melee(),
            new Ranged()
        };
    }

    // these properties are required, if you want to access predefined 
    // inventory slots in strongly-typed manner; they are NOT required for comparison
    public Chest ChestSlot
    {
        get { return (Chest)inventoryItems[0]; }
        set { inventoryItems[0] = value; }
    }

    public Helmet HelmetSlot
    {
        get { return (Helmet)inventoryItems[1]; }
        set { inventoryItems[1] = value; }
    }

    public Melee MeleeSlot
    {
        get { return (Melee)inventoryItems[2]; }
        set { inventoryItems[2] = value; }
    }

    public Ranged RangedSlot
    {
        get { return (Ranged)inventoryItems[3]; }
        set { inventoryItems[3] = value; }
    }

    // The comparison.
    public void Compare(Item newItem)
    {
        foreach (var item in inventoryItems)
        {
            if (item.CanCompareWith(newItem))
            {
                item.CompareWith(newItem);
            }
        }
    }
}

Note, that internally inventory uses a collection to store its items. This allow to perform batch operations with items (like comparison, implemented in Compare method).

Now, let's define some new types of items and test our inventory:

class SuperHelmet : Helmet
{
    public override int Defence
    {
        get { return 50; }
    }
}

class SuperMelee : Melee
{
    public override int Attack
    {
        get { return 200; }
    }
}

The test:

        var inventory = new Inventory();
        var superHelmet = new SuperHelmet();
        var superMeele = new SuperMelee();

        inventory.Compare(superHelmet);
        inventory.Compare(superMeele);
查看更多
登录 后发表回答