你如何改变继承方法的类型?

时间:2018-06-13 21:16:15

标签: c#

我想制作一个游戏,其中我有3种类型的角色,每种类型对于其他类型都有不同的攻击。我希望有一个带抽象方法的抽象类Character(它是一个要求)和3个继承该方法的类,然后为每个类型重载它。问题是我的Character类中的攻击方法采用了一个类型的Character参数,并且我必须在我的子类中使用一个空方法覆盖它,这使得该抽象类和方法的使用真的无用。那么我能做些什么呢?

    public abstract class Character
    {
        public abstract void Attack(Character t);
    }

    public class A :Character
    {
            public override void Attack(Character t){}

            public void Attack(A x)
            {
             /*instructions*/
            }

            public void Attack(B y)
            {
             /*instructions*/
            }

            public void Attack(C z)
            {
             /*instructions*/
            }
    }

B类和C类等。

我也希望避免这种情况:

    public abstract class Character
    {
        public abstract void Attack(Character c);
    }

    public class A :Character
    {
            public override void Attack(Character t)
            {
                A x = t as A
                if (x != null)
                {
                    /*instructions*/
                }

                B y = t as B
                if (y != null)
                {
                    /*instructions*/
                }

                C z = t as C
                if (z != null)
                {
                    /*instructions*/
                }
            }
    }

我希望我的问题尽管我的英语很清楚。

5 个答案:

答案 0 :(得分:1)

您可以使用dynamic中的一些“魔法”实现此调度:

abstract class Character {
    public void Attack(Character c) {
        ((dynamic)this).DoAttack((dynamic)c);
    }
}
class A : Character {
    public void DoAttack(A a) { Console.WriteLine("A attacks A"); }
    public void DoAttack(B b) { Console.WriteLine("A attacks B"); }
    public void DoAttack(C c) { Console.WriteLine("A attacks C"); }
}
class B : Character {
    public void DoAttack(A a) { Console.WriteLine("B attacks A"); }
    public void DoAttack(B b) { Console.WriteLine("B attacks B"); }
    public void DoAttack(C c) { Console.WriteLine("B attacks C"); }
}
class C : Character {
    public void DoAttack(A a) { Console.WriteLine("C attacks A"); }
    public void DoAttack(B b) { Console.WriteLine("C attacks B"); }
    public void DoAttack(C c) { Console.WriteLine("C attacks C"); }
}

注意thiscdynamic的强制转换,这使得运行时可以在不依赖继承结构的情况下找到相关的DoAttack覆盖。

Demo.

这种方法的一个优点是您可以根据需要添加新的实现:只要攻击仅限于“有效”类型的对象对,其余代码将继续有效。

这种方法的一个缺点是它不是静态类型的,这意味着它即使在没有处理所需交互的方法的情况下也会编译。您可以通过在DoAttack(Character c)类本身中提供Character的“默认”实现来缓解此问题。

答案 1 :(得分:1)

如果您想避免使用dasblinkenlight提供的动态调度解决方案,您可以通过实现访问者模式来解决此问题。我们需要为你的抽象类添加一个新方法。

public abstract class Character
{
    public int HP {get;set;}
    public abstract void Attack(Character t);
    public abstract void Accept(ICharacterVisitor visitor);
}

创建我们的ICharacterVisitor界面。

public interface ICharacterVisitor {
    void Visit(A a);
    void Visit(B b);
    void Visit(C c);
}

让我们继续并在ICharacterVisitor的术语中定义一些攻击角色A的规则。

public class AttackVisitorA : ICharacterVisitor
{
    private int _baseDamage;
    public AttackVisitorA(int baseDamage){
        _baseDamage = baseDamage;
    }
    public void Visit(A a)
    {
        // A does normal damage to A.
        a.HP -= _baseDamage;
    }

    public void Visit(B b)
    {
        // A does double damage to B.
        b.HP -= (_baseDamage * 2);
    }

    public void Visit(C c)
    {
        // A does half damage to C.
        c.HP -= (_baseDamage / 2);
    }
}

现在我们可以实现A,B和C字符。我已将执行攻击B和C作为练习给读者。

public class A : Character
{
    public override void Accept(ICharacterVisitor visitor)
    {
        visitor.Visit(this);
    }

    public override void Attack(Character t)
    {
        var damage = 15;
        t.Accept(new AttackVisitorA(damage));
    }
}

public class B : Character
{
    public override void Accept(ICharacterVisitor visitor)
    {
        visitor.Visit(this);
    }

    public override void Attack(Character t)
    {
        throw new NotImplementedException();
    }
}

public class C : Character
{
    public override void Accept(ICharacterVisitor visitor)
    {
        visitor.Visit(this);
    }

    public override void Attack(Character t)
    {
        throw new NotImplementedException();
    }
}

现在,我们可以非常简单地对来自其他角色的角色进行攻击:

void Main()
{
    var a = new A { HP = 100 };
    var b = new B { HP = 100 };
    var c = new C { HP = 100 };
    a.Attack(a); // stop hitting yourself
    a.Attack(b);
    a.Attack(c);
    Console.WriteLine(a.HP); // 85
    Console.WriteLine(b.HP); // 70
    Console.WriteLine(c.HP); // 93
}

逻辑与对象层次结构有效地分离。您可以实现一个遵循标准规则的DefaultAttackVisitor,或者为每个角色类型设置一个AttackVisitor,或者可能是一些基于武器或法术的更复杂的规则。无论哪种方式,你最终都得到了一套相当干净且易于交换的逻辑,用于解决类之间的攻击。<​​/ p>

使用Dynamic Dispatch实现访问者将为您提供更多的编译时类型安全性。如果在某些时候你要添加一个新的字符类型D,那么在你实现D的Accept方法时,如果没有确保你的访问者实现被更新为包含D的逻辑,你将无法进行编译。

使用动态调度,如果攻击类型没有添加Attack(D d)重载,则会针对任何新添加的字符类型触发您的直通方法。

也就是说,动态调度的实现开销要低得多,但是你放弃了一些编译时类型的安全性。您必须评估哪种权衡对您的用例更重要。

答案 2 :(得分:0)

使用已经内置到C#typeof()这里是一些示例代码。

public class Program
{
    public static void Main()
    {
        A a = new A();
        B b = new B();

        a.Attack(b);
        b.Attack(a);
        Console.WriteLine(typeof(A));
        Console.WriteLine(typeof(B));

        Console.WriteLine(typeof(A) == a.GetType());
        Console.WriteLine(typeof(B) == a.GetType());
        Console.ReadLine();
    }

    public abstract class Character
    {
        public abstract void Attack(Character c);
    }

    public class A : Character
    {
        public override void Attack(Character t)
        {
            if (t.GetType() == typeof(A))
            {
                Console.WriteLine("A attacked type A");
                return;
            }

            if (t.GetType() == typeof(B))
            {
                Console.WriteLine("A attacked type B");
                return;
            }

            if (t.GetType() == typeof(C))
            {
                Console.WriteLine("A attacked type C");
                return;
            }

        }
    }

    public class B : Character
    {
        public override void Attack(Character t)
        {
            if (t.GetType() == typeof(A))
            {
                Console.WriteLine("B attacked type A");
                return;
            }

            if (t.GetType() == typeof(B))
            {
                Console.WriteLine("B attacked type B");
                return;
            }

            if (t.GetType() == typeof(C))
            {
                Console.WriteLine("B attacked type C");
                return;
            }


        }

    }
    public class C : Character
    {
        public override void Attack(Character t)
        {
            if (t.GetType() == typeof(A))
            {
                Console.WriteLine("C attacked type A");
                return;
            }

            if (t.GetType() == typeof(B))
            {
                Console.WriteLine("C attacked type B");
                return;
            }

            if (t.GetType() == typeof(C))
            {
                Console.WriteLine("C attacked type C");
                return;
            }

        }
    }


}

答案 3 :(得分:0)

所有这些答案都非常有趣,即使有些概念超出了我的想法。 阅读他们我终于意识到没有真正的问题(不知道为什么我创建了一个,但你知道......),它看起来不是很好但是我选择了以下最简单的解决方案:

我只考虑最常见的过程,并将其作为继承抽象类的所有类的方法。然后我可以为那些具有特定攻击方式的人指定方法。 所以,让我们说当A类角色攻击A类或B类角色的方式相同但攻击C类不同的角色时,我们只需要做多态:

public abstract class Character
{
    public abstract void Attack(Character t);
}

public class A :Character
{
        public override void Attack(Character t)
        {
         /*instructions for the attacking of a character of type A or B*/
        }

        public void Attack(C z)
        {
         /*instructions for the attacking of a type C character*/
        }
}

一般方法(带有字符参数的方法)将用于任何字符类型对象(或从字符继承的对象),除非它是C类对象/字符;在这种情况下,它将被分派到Attack方法,该方法将类型C对象/字符作为参数。

如果所有树类型都以不同的方式受到攻击,那么我们可以随意选择其中一个树类,以便在抽象方法签名之后实现:

public abstract class Character
{
    public abstract void Attack(Character t);
}

public class A :Character
{
        public override void Attack(Character t)
        {
         /*instructions for the attacking of a character of type A*/
        }

        public void Attack(B y)
        {
         /*instructions for the attacking of a type B character*/
        }

        public void Attack(C z)
        {
         /*instructions for the attacking of a type C character*/
        }
}

好吧,我不知道这是不是太过分了,但它确实有效。 dynamic,接口或泛型类型的使用可能是好事,但实际上没有什么可做的。

感谢您的观点,请告诉我一些事情是否正确。

对于任何读者来说,我建议看看Eric Lippert在他的博客上发表的一篇文章的第3部分中提出的关于密切问题(实际存在的问题)的优雅解决方案。点击此链接:https://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/

答案 4 :(得分:-1)

既然你知道你只有3种不同的类型,为什么不在Charackter类中为每种类型创建3个抽象方法?

    public abstract class Character
        {
            public abstract void Attack(A a);
            public abstract void Attack(B a);
            public abstract void Attack(C a);
        }