Inherited class with methods taking Child type as a parameter: wrong method being called

坚强是说给别人听的谎言 提交于 2019-12-12 19:28:54

问题


I am making a card game, and I have several scripts that control card behavior.

public class BasicCardModel : Draggable {
    public int hitPoints;
    public GameObject cardObject;
    public static string cardName = "Basic Card";

    public CardStatus cardStatus = CardStatus.None;

    public void copyAttributes(BasicCardModel bcm) {
        Debug.Log("Calling basic copy attributes");
        hitPoints = bcm.hitPoints;
    }
    ...
}

I have several specialized cards similar to below:

public class AttackCardModel : BasicCardModel {
    public int attackStrength;
    public AttackType attackType;

    public void copyAttributes(AttackCardModel acm) {
        base.copyAttributes(acm);
        attackType = acm.attackType;
        Debug.Log("Attack strength = " + acm.attackStrength);
        attackStrength = acm.attackStrength;
    }
}

I have a controller object that generates these cards:

public class GameController : MonoBehaviour {
    public GameObject basicCard, attackCard, defenseCard, eventCard, masterCard;
    public GameObject topPlayerDeck, topPlayerHand, topPlayerField;
    public GameObject bottomPlayerDeck, bottomPlayerHand, bottomPlayerField;
    public GameObject eventDeck;
    // Use this for initialization
    void Start () {
        // Link controller to game objects
        topPlayerDeck = GameObject.Find("TopPlayerDeck");
        topPlayerHand = GameObject.Find("TopPlayerHand");
        topPlayerField = GameObject.Find("TopPlayerField");
        bottomPlayerDeck = GameObject.Find("BottomPlayerDeck");
        bottomPlayerHand = GameObject.Find("BottomPlayerHand");
        bottomPlayerField = GameObject.Find("BottomPlayerField");
        eventDeck = GameObject.Find("EventDeck");

        CardCollection cards = generateCards();

        foreach (BasicCardModel card in cards.cards) {
            if(card is AttackCardModel) {
                createCard<AttackCardModel>(topPlayerHand, card as AttackCardModel, Player.Top, CardStatus.Hand);
                createCard<AttackCardModel>(bottomPlayerHand, card as AttackCardModel, Player.Bottom, CardStatus.Hand);
            }
            else if(card is DefenseCardModel) {
                createCard<DefenseCardModel>(topPlayerHand, card as DefenseCardModel, Player.Top, CardStatus.Hand);
                createCard<DefenseCardModel>(bottomPlayerHand, card as DefenseCardModel, Player.Bottom, CardStatus.Hand);
            }
            else {
                createCard<BasicCardModel>(topPlayerHand, card as BasicCardModel, Player.Top, CardStatus.Hand);
                createCard<BasicCardModel>(bottomPlayerHand, card as BasicCardModel, Player.Bottom, CardStatus.Hand);
            }
        }

        /*
        for(int i = 0; i < 2; i++) {
            createCard<AttackCardModel>(topPlayerHand, Player.Top, CardStatus.Hand);
            createCard<AttackCardModel>(bottomPlayerHand, Player.Bottom, CardStatus.Hand);
        }
        for (int i = 0; i < 2; i++) {
            createCard<DefenseCardModel>(topPlayerHand, Player.Top, CardStatus.Hand);
            createCard<DefenseCardModel>(bottomPlayerHand, Player.Bottom, CardStatus.Hand);
        }
        */
    }

    // Update is called once per frame
    void Update () {

    }

    // For testing, have a CardCollection passed in later
    public CardCollection generateCards() {
        CardCollection cards = new CardCollection();
        //AttackCardModel testcard = new AttackCardModel(4, AttackType.SQLInjection, 3);
        cards.cards.Add(new AttackCardModel(4, AttackType.SQLInjection, 3));
        cards.cards.Add(new DefenseCardModel(5, AttackType.SQLInjection, 1));
        //Debug.Log(testcard.attackStrength + "is attack strength");
        return cards;
    }

    public void createCard<T>(GameObject whereToPut, T objectToCopy, Player player, CardStatus cardStatus) where T : BasicCardModel {
        GameObject new_card = Instantiate(basicCard);
        new_card.transform.SetParent(whereToPut.transform, false);
        Destroy(new_card.GetComponent<BasicCardModel>());
        new_card.AddComponent<T>();
        --->new_card.GetComponent<T>().copyAttributes(objectToCopy); <---
        new_card.GetComponent<T>().linkModelToCardObject(new_card);
        new_card.GetComponent<T>().setUpCard<T>(player, cardStatus);
    }
}

I am having trouble with this line (near the end of the controller object): new_card.GetComponent<T>().copyAttributes(objectToCopy);

Instead of calling the child method for copyAttributes, it calls the parent method, and thus does not copy the attributes that I want.

In my previous question about this, one person suggested using dynamic typing as a solution. However, even though GetComponent<ParentClass> should get the child types, it doesn't work for some reason, so I need to use generics.

How can I force it to call the child method instead of the parent method?


回答1:


Make your BasicCardModel's copyAttributes method virtual.

public class BasicCardModel : Draggable {
    // ...    
    public virtual void copyAttributes(BasicCardModel bcm) {
        // ...    
    }
    // ...    
}

Then override it AttackCardModel, and cast the card model to the derived type before copying additional attributes:

public override void copyAttributes(BasicCardModel bcm) {
    base.copyAttributes(acm);
    var acm = bcm as AttackCardModel;
    if (acm != null) {
        attackType = acm.attackType;
        Debug.Log("Attack strength = " + acm.attackStrength);
        attackStrength = acm.attackStrength;
    }
}



回答2:


You need to reflect the hierarchy in your types - one way is to create an interface and add a constraint for it in your Create method:

public interface ICopyableFrom<T>
{
     void CopyAttributes(T src);
}

public void createCard<T>(GameObject whereToPut, T objectToCopy, Player player, CardStatus cardStatus) where T : BasicCardModel, ICopyableFrom<T>
{
        GameObject new_card = Instantiate(basicCard);
        new_card.transform.SetParent(whereToPut.transform, false);
        Destroy(new_card.GetComponent<BasicCardModel>());
        new_card.AddComponent<T>();
        new_card.GetComponent<T>().CopyAttributes(objectToCopy);
        new_card.GetComponent<T>().linkModelToCardObject(new_card);
        new_card.GetComponent<T>().setUpCard<T>(player, cardStatus);
}

which you then need to implement in your classes:

public class AttackCardModel : BasicCardModel, ICopyableFrom<AttackCardModel>
{
    public void CopyAttributes(AttackCardModel src)
    {
    }
}


来源:https://stackoverflow.com/questions/33768952/inherited-class-with-methods-taking-child-type-as-a-parameter-wrong-method-bein

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!