简体   繁体   中英

Generics and Inheritance: Using generics with a base-class and its sub-classes

I've been trying to develop an UI item dragging and dropping system for the past few days and make it as flexible as possible (it's being developed using Unity 3D although that's not exactly relevant to my problem). Here's the code before I get to explaining exactly what the issue is:

public class ItemHandler<ItemType> : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IHoverHandler where ItemType : Item {

     public ItemType item { get; protected set; }

     ...
}

public abstract class SlotHandler<ItemType> : MonoBehaviour, IDropHandler where ItemType : Item {

    ...

    public ItemHandler<ItemType> itemHandler { get; protected set; }

    ...

    public void OnDrop(PointerEventData eventData) {

        // Retrieve the slots
        SlotHandler<Item> originalSlotHandler = ItemManager.draggedItemParent.GetComponent<SlotHandler<Item>>();
        SlotHandler<ItemType> destinationSlotHandler = this;

        // Retrieve the handlers from their items
        ItemHandler<Item> originalItemHandler = originalSlotHandler.itemObject != null ? originalSlotHandler.itemObject.GetComponent<ItemHandler<Item>>() : null;
        ItemHandler<ItemType> destinationItemHandler = destinationSlotHandler.itemObject != null ? destinationSlotHandler.itemObject.GetComponent<ItemHandler<ItemType>>() : null;

        // Add the original item to the destination slot
        bool canAddToDestination = originalSlotHandler is SlotHandler<ItemType> || originalSlotHandler is SlotHandler<Item>;
        bool canRemoveFromOrigin = originalSlotHandler is SlotHandler<ItemType> || destinationSlotHandler is SlotHandler<Item>;

        // Swap the items
        if(canAddToDestination == true && canRemoveFromOrigin == true) {

            // Remove the old items
            originalSlotHandler.RemoveItem();
            destinationSlotHandler.RemoveItem();

            // Add the new items
            originalSlotHandler.AddItem(destinationItemHandler);
            destinationSlotHandler.AddItem(originalItemHandler);

            ...
        }

        ...
    }

    public virtual void AddItem(ItemHandler<ItemType> itemHandler) {

        this.itemHandler = itemHandler;
    }
}

The idea behind this is to have ItemHandler of a given type (eg ItemHandler< Gem >, ItemHandler< Weapon > etc.) and then only allow them to be dropped into SlotHandlers of the same type OR of the same base type.

  • ItemHandler< Gem > goes into SlotHandler< Gem > and SlotHandler< Item >.
  • ItemHandler< Weapon > goes into SlotHandler< Weapon > and SlotHandler< Item >.

I tried doing things this way because I want to limit in which slots I'm able to drop specific items.

The problem comes from the fact that I have to take into account a specific type of slot, SlotHandler< Item > (the Item class you see in the code snippet which is the base class for all Items). The inventory slots should be able to hold every type of item.

Now, since ItemHandler< Gem > isn't a subclass of ItemHandler< Item > (or any other derived type of Item) this won't work. Now you might say why not just store Item instead of storing a Generic ItemType? I would but the idea here is to derive SlotHandler and have specific behaviour in each sub-class (dropping items into different types of slots should do different things).

I think I understand why this won't work the way I'm trying to do it but I can't find a solution I feel comfortable with so I thought I'd try and ask for some help. Sorry If I'm not being clear enough with the problem, please let me know if I should clear something up.

Thanks in advance for any help trying to solve my problem!

To me it seems that although you need to keep track of the types, all you really need is a record of the parents' type (and their parents) for each item. Rather than using the type itself in a programming sense why not just use a string label and create a list in the base class for the hierarchy of valid types for the item. That way you can have complex connections and organization and only need to compare a single string to a list to see if it's valid.

using UnityEngine;
using System.Collections.Generic;

public class Item {

  public List<string> validInvTypes = new List<string>();

  //...
}

public class Gem : Item {

  //...
}

public class Ruby : Gem {

  public Ruby()
  {
    validInvTypes.Add("Ruby");
    validInvTypes.Add("Gem");
    validInvTypes.Add("Item");
  }

  //...
}

public abstract class SlotHandler<ItemType> : MonoBehaviour, IDropHandler where ItemType : Item {

  //...

  public void OnDrop(PointerEventData eventData)
  {
    //...

    //string currentInvSlot
    //Item droppedItem

    if (droppedItem.validInvTypes.Contains(currentInvSlot))
      // Swap the items...
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM